Merge branch 'bug/JAL-3141_backupfiles_prefs_widget_disable_bug' into merge/develop_b...
authorBen Soares <bsoares@dundee.ac.uk>
Wed, 22 May 2019 11:18:01 +0000 (12:18 +0100)
committerBen Soares <bsoares@dundee.ac.uk>
Wed, 22 May 2019 11:18:01 +0000 (12:18 +0100)
Merging three unmerged commits

629 files changed:
.classpath [deleted file]
.gitignore
.project [deleted file]
.settings/org.eclipse.buildship.core.prefs [new file with mode: 0644]
.settings/org.eclipse.jdt.core.jalview.prefs [moved from .settings/org.eclipse.jdt.core.prefs with 99% similarity]
.settings/org.eclipse.jdt.groovy.core.prefs [new file with mode: 0644]
.settings/org.eclipse.jdt.ui.prefs
JAVA-11-README [new file with mode: 0644]
authors.props [new file with mode: 0644]
build.gradle [new file with mode: 0644]
build.xml [deleted file]
doc/building-OLD.html [moved from doc/building.html with 81% similarity]
examples/exampleFile.jvp [new file with mode: 0644]
examples/groovy/colourConserved.groovy
examples/groovy/colourSchemes.groovy
examples/groovy/colourUnconserved.groovy
getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar [new file with mode: 0644]
getdown/lib/getdown-launcher.jar [new file with mode: 0644]
getdown/src/getdown/.project-MOVED [new file with mode: 0644]
getdown/src/getdown/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs [new file with mode: 0644]
getdown/src/getdown/.travis.yml [new file with mode: 0644]
getdown/src/getdown/AUTHORS [new file with mode: 0644]
getdown/src/getdown/CHANGELOG.md [new file with mode: 0644]
getdown/src/getdown/LICENSE [new file with mode: 0644]
getdown/src/getdown/README.md [new file with mode: 0644]
getdown/src/getdown/ant/.project-MOVED [new file with mode: 0644]
getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs [new file with mode: 0644]
getdown/src/getdown/ant/pom.xml [new file with mode: 0644]
getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java [new file with mode: 0644]
getdown/src/getdown/bin/differ [new file with mode: 0755]
getdown/src/getdown/bin/patcher [new file with mode: 0755]
getdown/src/getdown/core/.project-MOVED [new file with mode: 0644]
getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs [new file with mode: 0644]
getdown/src/getdown/core/pom.xml [new file with mode: 0644]
getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/background.png [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/foo.jar [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/getdown.txt [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/script.sh [new file with mode: 0644]
getdown/src/getdown/core/src/it/resources/testapp/testapp.jar [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java [new file with mode: 0644]
getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker [new file with mode: 0644]
getdown/src/getdown/launcher/.project-MOVED [new file with mode: 0644]
getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs [new file with mode: 0644]
getdown/src/getdown/launcher/pom.xml [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties [new file with mode: 0644]
getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties [new file with mode: 0644]
getdown/src/getdown/lib/SOURCE_HEADER [new file with mode: 0644]
getdown/src/getdown/lib/commons-compress-1.18.jar [new file with mode: 0644]
getdown/src/getdown/lib/jRegistryKey.dll [new file with mode: 0644]
getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar [new file with mode: 0644]
getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom [new file with mode: 0644]
getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml [new file with mode: 0644]
getdown/src/getdown/lib/manifest.mf [new file with mode: 0644]
getdown/src/getdown/mvn_cmd [new file with mode: 0644]
getdown/src/getdown/pom.xml [new file with mode: 0644]
gradle.properties [new file with mode: 0644]
help/help/help.hs [moved from help/help.hs with 100% similarity]
help/help/help.jhm [moved from help/help.jhm with 100% similarity]
help/help/helpTOC.xml [moved from help/helpTOC.xml with 100% similarity]
help/help/html/.cvsignore [moved from help/html/.cvsignore with 100% similarity]
help/help/html/Jalview_Logo.png [moved from help/html/Jalview_Logo.png with 100% similarity]
help/help/html/align.jpg [moved from help/html/align.jpg with 100% similarity]
help/help/html/calculations/calculatedialog.png [moved from help/html/calculations/calculatedialog.png with 100% similarity]
help/help/html/calculations/calculations.html [moved from help/html/calculations/calculations.html with 100% similarity]
help/help/html/calculations/consensus.html [moved from help/html/calculations/consensus.html with 100% similarity]
help/help/html/calculations/conservation.html [moved from help/html/calculations/conservation.html with 100% similarity]
help/help/html/calculations/pairwise.html [moved from help/html/calculations/pairwise.html with 100% similarity]
help/help/html/calculations/pca.html [moved from help/html/calculations/pca.html with 96% similarity]
help/help/html/calculations/pcaviewer.gif [moved from help/html/calculations/pcaviewer.gif with 100% similarity]
help/help/html/calculations/quality.html [moved from help/html/calculations/quality.html with 100% similarity]
help/help/html/calculations/recoverInputdata.html [moved from help/html/calculations/recoverInputdata.html with 100% similarity]
help/help/html/calculations/redundancy.html [moved from help/html/calculations/redundancy.html with 100% similarity]
help/help/html/calculations/referenceseq.html [moved from help/html/calculations/referenceseq.html with 100% similarity]
help/help/html/calculations/scorematrices.html [moved from help/html/calculations/scorematrices.html with 100% similarity]
help/help/html/calculations/sorting.html [moved from help/html/calculations/sorting.html with 100% similarity]
help/help/html/calculations/structureconsensus.html [moved from help/html/calculations/structureconsensus.html with 100% similarity]
help/help/html/calculations/tree.html [moved from help/html/calculations/tree.html with 100% similarity]
help/help/html/calculations/treeviewer.html [moved from help/html/calculations/treeviewer.html with 100% similarity]
help/help/html/colourSchemes/.cvsignore [moved from help/html/colourSchemes/.cvsignore with 100% similarity]
help/help/html/colourSchemes/abovePID.html [moved from help/html/colourSchemes/abovePID.html with 100% similarity]
help/help/html/colourSchemes/annotationColourSetting.gif [moved from help/html/colourSchemes/annotationColourSetting.gif with 100% similarity]
help/help/html/colourSchemes/annotationColouring.html [moved from help/html/colourSchemes/annotationColouring.html with 100% similarity]
help/help/html/colourSchemes/annotationColours.gif [moved from help/html/colourSchemes/annotationColours.gif with 100% similarity]
help/help/html/colourSchemes/blosum.html [moved from help/html/colourSchemes/blosum.html with 100% similarity]
help/help/html/colourSchemes/buried.html [moved from help/html/colourSchemes/buried.html with 100% similarity]
help/help/html/colourSchemes/clustal.html [moved from help/html/colourSchemes/clustal.html with 100% similarity]
help/help/html/colourSchemes/colbytcoffee.png [moved from help/html/colourSchemes/colbytcoffee.png with 100% similarity]
help/help/html/colourSchemes/conservation.html [moved from help/html/colourSchemes/conservation.html with 100% similarity]
help/help/html/colourSchemes/helix.html [moved from help/html/colourSchemes/helix.html with 100% similarity]
help/help/html/colourSchemes/hydrophobic.html [moved from help/html/colourSchemes/hydrophobic.html with 100% similarity]
help/help/html/colourSchemes/index.html [moved from help/html/colourSchemes/index.html with 100% similarity]
help/help/html/colourSchemes/nucleotide.html [moved from help/html/colourSchemes/nucleotide.html with 100% similarity]
help/help/html/colourSchemes/pid.html [moved from help/html/colourSchemes/pid.html with 100% similarity]
help/help/html/colourSchemes/purinepyrimidine.html [moved from help/html/colourSchemes/purinepyrimidine.html with 100% similarity]
help/help/html/colourSchemes/rnahelicesColouring.html [moved from help/html/colourSchemes/rnahelicesColouring.html with 100% similarity]
help/help/html/colourSchemes/rnahelicescoloring.png [moved from help/html/colourSchemes/rnahelicescoloring.png with 100% similarity]
help/help/html/colourSchemes/strand.html [moved from help/html/colourSchemes/strand.html with 100% similarity]
help/help/html/colourSchemes/taylor.html [moved from help/html/colourSchemes/taylor.html with 100% similarity]
help/help/html/colourSchemes/textcolour.gif [moved from help/html/colourSchemes/textcolour.gif with 100% similarity]
help/help/html/colourSchemes/textcolour.html [moved from help/html/colourSchemes/textcolour.html with 100% similarity]
help/help/html/colourSchemes/turn.html [moved from help/html/colourSchemes/turn.html with 100% similarity]
help/help/html/colourSchemes/user.html [moved from help/html/colourSchemes/user.html with 100% similarity]
help/help/html/colourSchemes/userDefined.gif [moved from help/html/colourSchemes/userDefined.gif with 100% similarity]
help/help/html/colourSchemes/userDefined_java7.gif [moved from help/html/colourSchemes/userDefined_java7.gif with 100% similarity]
help/help/html/colourSchemes/zappo.html [moved from help/html/colourSchemes/zappo.html with 100% similarity]
help/help/html/editing/.cvsignore [moved from help/html/editing/.cvsignore with 100% similarity]
help/help/html/editing/editing.jpg [moved from help/html/editing/editing.jpg with 100% similarity]
help/help/html/editing/index.html [moved from help/html/editing/index.html with 100% similarity]
help/help/html/features/.cvsignore [moved from help/html/features/.cvsignore with 100% similarity]
help/help/html/features/AnnotationColumnSelectionWithSM.png [moved from help/html/features/AnnotationColumnSelectionWithSM.png with 100% similarity]
help/help/html/features/AnnotationColumnSelectionWithoutSM.gif [moved from help/html/features/AnnotationColumnSelectionWithoutSM.gif with 100% similarity]
help/help/html/features/amendfeature.gif [moved from help/html/features/amendfeature.gif with 100% similarity]
help/help/html/features/annotation.html [moved from help/html/features/annotation.html with 100% similarity]
help/help/html/features/annotationsFormat.html [moved from help/html/features/annotationsFormat.html with 100% similarity]
help/help/html/features/bioJsonFormat.html [moved from help/html/features/bioJsonFormat.html with 100% similarity]
help/help/html/features/biojsmsa.html [moved from help/html/features/biojsmsa.html with 100% similarity]
help/help/html/features/chimera.html [moved from help/html/features/chimera.html with 100% similarity]
help/help/html/features/clarguments.html [moved from help/html/features/clarguments.html with 100% similarity]
help/help/html/features/codingfeatures.html [moved from help/html/features/codingfeatures.html with 100% similarity]
help/help/html/features/columnFilterByAnnotation.html [moved from help/html/features/columnFilterByAnnotation.html with 100% similarity]
help/help/html/features/commandline.html [moved from help/html/features/commandline.html with 100% similarity]
help/help/html/features/creatinFeatures.html [moved from help/html/features/creatinFeatures.html with 100% similarity]
help/help/html/features/crnewfeature.gif [moved from help/html/features/crnewfeature.gif with 100% similarity]
help/help/html/features/cursorMode.html [moved from help/html/features/cursorMode.html with 100% similarity]
help/help/html/features/editingFeatures.html [moved from help/html/features/editingFeatures.html with 100% similarity]
help/help/html/features/ensemblsequencefetcher.html [moved from help/html/features/ensemblsequencefetcher.html with 100% similarity]
help/help/html/features/fcsgtthan.gif [moved from help/html/features/fcsgtthan.gif with 100% similarity]
help/help/html/features/fcsltthan.gif [moved from help/html/features/fcsltthan.gif with 100% similarity]
help/help/html/features/fcsminmax.gif [moved from help/html/features/fcsminmax.gif with 100% similarity]
help/help/html/features/fcsntlabel.gif [moved from help/html/features/fcsntlabel.gif with 100% similarity]
help/help/html/features/featureSettings.gif [moved from help/html/features/featureSettings.gif with 100% similarity]
help/help/html/features/featurecoloursettings.gif [moved from help/html/features/featurecoloursettings.gif with 100% similarity]
help/help/html/features/featuresFormat.html [moved from help/html/features/featuresFormat.html with 100% similarity]
help/help/html/features/featureschemes.html [moved from help/html/features/featureschemes.html with 100% similarity]
help/help/html/features/featuresettings.html [moved from help/html/features/featuresettings.html with 99% similarity]
help/help/html/features/groovy.html [moved from help/html/features/groovy.html with 100% similarity]
help/help/html/features/hiddenRegions.html [moved from help/html/features/hiddenRegions.html with 100% similarity]
help/help/html/features/jalarchive.html [moved from help/html/features/jalarchive.html with 100% similarity]
help/help/html/features/jmol.html [moved from help/html/features/jmol.html with 100% similarity]
help/help/html/features/mmcif.html [moved from help/html/features/mmcif.html with 100% similarity]
help/help/html/features/multipleViews.html [moved from help/html/features/multipleViews.html with 100% similarity]
help/help/html/features/newkeystrokes.html [moved from help/html/features/newkeystrokes.html with 100% similarity]
help/help/html/features/overview.html [moved from help/html/features/overview.html with 100% similarity]
help/help/html/features/overview.png [moved from help/html/features/overview.png with 100% similarity]
help/help/html/features/pdbseqfetcher.png [moved from help/html/features/pdbseqfetcher.png with 100% similarity]
help/help/html/features/pdbsequencefetcher.html [moved from help/html/features/pdbsequencefetcher.html with 100% similarity]
help/help/html/features/pdbviewer.html [moved from help/html/features/pdbviewer.html with 100% similarity]
help/help/html/features/preferences.html [moved from help/html/features/preferences.html with 100% similarity]
help/help/html/features/schooser_enter-id.png [moved from help/html/features/schooser_enter-id.png with 100% similarity]
help/help/html/features/schooser_main.png [moved from help/html/features/schooser_main.png with 100% similarity]
help/help/html/features/search.html [moved from help/html/features/search.html with 100% similarity]
help/help/html/features/search.png [moved from help/html/features/search.png with 100% similarity]
help/help/html/features/searchclearhist.png [moved from help/html/features/searchclearhist.png with 100% similarity]
help/help/html/features/searchhist.png [moved from help/html/features/searchhist.png with 100% similarity]
help/help/html/features/selectfetchdb.gif [moved from help/html/features/selectfetchdb.gif with 100% similarity]
help/help/html/features/seqfeatures.html [moved from help/html/features/seqfeatures.html with 100% similarity]
help/help/html/features/seqfetch.html [moved from help/html/features/seqfetch.html with 100% similarity]
help/help/html/features/seqfetcher.gif [moved from help/html/features/seqfetcher.gif with 100% similarity]
help/help/html/features/seqmappings.html [moved from help/html/features/seqmappings.html with 100% similarity]
help/help/html/features/sifts_mapping_output.png [moved from help/html/features/sifts_mapping_output.png with 100% similarity]
help/help/html/features/siftsmapping.html [moved from help/html/features/siftsmapping.html with 100% similarity]
help/help/html/features/splitView.html [moved from help/html/features/splitView.html with 100% similarity]
help/help/html/features/structurechooser.html [moved from help/html/features/structurechooser.html with 100% similarity]
help/help/html/features/uniprotqueryfields.html [moved from help/html/features/uniprotqueryfields.html with 100% similarity]
help/help/html/features/uniprotseqfetcher.png [moved from help/html/features/uniprotseqfetcher.png with 100% similarity]
help/help/html/features/uniprotsequencefetcher.html [moved from help/html/features/uniprotsequencefetcher.html with 100% similarity]
help/help/html/features/varna.html [moved from help/html/features/varna.html with 100% similarity]
help/help/html/features/viewingpdbs.html [moved from help/html/features/viewingpdbs.html with 100% similarity]
help/help/html/features/wrap.html [moved from help/html/features/wrap.html with 100% similarity]
help/help/html/features/xsspannotation.html [moved from help/html/features/xsspannotation.html with 100% similarity]
help/help/html/groovy/featuresCounter.html [moved from help/html/groovy/featuresCounter.html with 100% similarity]
help/help/html/index.html [moved from help/html/index.html with 100% similarity]
help/help/html/io/.cvsignore [moved from help/html/io/.cvsignore with 100% similarity]
help/help/html/io/export.html [moved from help/html/io/export.html with 95% similarity]
help/help/html/io/exportseqreport.html [moved from help/html/io/exportseqreport.html with 100% similarity]
help/help/html/io/file.png [moved from help/html/io/file.png with 100% similarity]
help/help/html/io/fileformats.html [moved from help/html/io/fileformats.html with 100% similarity]
help/help/html/io/index.html [moved from help/html/io/index.html with 100% similarity]
help/help/html/io/modellerpir.html [moved from help/html/io/modellerpir.html with 100% similarity]
help/help/html/io/seqreport.gif [moved from help/html/io/seqreport.gif with 100% similarity]
help/help/html/io/tcoffeescores.html [moved from help/html/io/tcoffeescores.html with 100% similarity]
help/help/html/keys.html [moved from help/html/keys.html with 100% similarity]
help/help/html/memory.html [moved from help/html/memory.html with 100% similarity]
help/help/html/menus/alignmentMenu.html [moved from help/html/menus/alignmentMenu.html with 100% similarity]
help/help/html/menus/alwannotation.html [moved from help/html/menus/alwannotation.html with 100% similarity]
help/help/html/menus/alwannotationpanel.html [moved from help/html/menus/alwannotationpanel.html with 100% similarity]
help/help/html/menus/alwcalculate.html [moved from help/html/menus/alwcalculate.html with 92% similarity]
help/help/html/menus/alwcolour.html [moved from help/html/menus/alwcolour.html with 100% similarity]
help/help/html/menus/alwedit.html [moved from help/html/menus/alwedit.html with 100% similarity]
help/help/html/menus/alwfile.html [moved from help/html/menus/alwfile.html with 94% similarity]
help/help/html/menus/alwformat.html [moved from help/html/menus/alwformat.html with 100% similarity]
help/help/html/menus/alwselect.html [moved from help/html/menus/alwselect.html with 100% similarity]
help/help/html/menus/alwview.html [moved from help/html/menus/alwview.html with 100% similarity]
help/help/html/menus/desktopMenu.html [moved from help/html/menus/desktopMenu.html with 100% similarity]
help/help/html/menus/index.html [moved from help/html/menus/index.html with 100% similarity]
help/help/html/menus/popupMenu.html [moved from help/html/menus/popupMenu.html with 100% similarity]
help/help/html/menus/wsmenu.html [moved from help/html/menus/wsmenu.html with 100% similarity]
help/help/html/misc/.cvsignore [moved from help/html/misc/.cvsignore with 100% similarity]
help/help/html/misc/aaproperties.html [moved from help/html/misc/aaproperties.html with 100% similarity]
help/help/html/misc/ala.jpg [moved from help/html/misc/ala.jpg with 100% similarity]
help/help/html/misc/aminoAcids.html [moved from help/html/misc/aminoAcids.html with 100% similarity]
help/help/html/misc/arg.jpg [moved from help/html/misc/arg.jpg with 100% similarity]
help/help/html/misc/asparagine.jpg [moved from help/html/misc/asparagine.jpg with 100% similarity]
help/help/html/misc/aspartate.jpg [moved from help/html/misc/aspartate.jpg with 100% similarity]
help/help/html/misc/cys.jpg [moved from help/html/misc/cys.jpg with 100% similarity]
help/help/html/misc/geneticCode.html [moved from help/html/misc/geneticCode.html with 100% similarity]
help/help/html/misc/glutamate.jpg [moved from help/html/misc/glutamate.jpg with 100% similarity]
help/help/html/misc/glutamine.jpg [moved from help/html/misc/glutamine.jpg with 100% similarity]
help/help/html/misc/gly.jpg [moved from help/html/misc/gly.jpg with 100% similarity]
help/help/html/misc/his.jpg [moved from help/html/misc/his.jpg with 100% similarity]
help/help/html/misc/iso.jpg [moved from help/html/misc/iso.jpg with 100% similarity]
help/help/html/misc/leu.jpg [moved from help/html/misc/leu.jpg with 100% similarity]
help/help/html/misc/lys.jpg [moved from help/html/misc/lys.jpg with 100% similarity]
help/help/html/misc/met.jpg [moved from help/html/misc/met.jpg with 100% similarity]
help/help/html/misc/phe.jpg [moved from help/html/misc/phe.jpg with 100% similarity]
help/help/html/misc/pro.jpg [moved from help/html/misc/pro.jpg with 100% similarity]
help/help/html/misc/properties.gif [moved from help/html/misc/properties.gif with 100% similarity]
help/help/html/misc/ser.jpg [moved from help/html/misc/ser.jpg with 100% similarity]
help/help/html/misc/thr.jpg [moved from help/html/misc/thr.jpg with 100% similarity]
help/help/html/misc/tryp.jpg [moved from help/html/misc/tryp.jpg with 100% similarity]
help/help/html/misc/tyr.jpg [moved from help/html/misc/tyr.jpg with 100% similarity]
help/help/html/misc/val.jpg [moved from help/html/misc/val.jpg with 100% similarity]
help/help/html/na/index.html [moved from help/html/na/index.html with 100% similarity]
help/help/html/privacy.html [moved from help/html/privacy.html with 100% similarity]
help/help/html/releases.html [moved from help/html/releases.html with 94% similarity]
help/help/html/vamsas/index.html [moved from help/html/vamsas/index.html with 100% similarity]
help/help/html/webServices/.cvsignore [moved from help/html/webServices/.cvsignore with 100% similarity]
help/help/html/webServices/AACon.html [moved from help/html/webServices/AACon.html with 100% similarity]
help/help/html/webServices/JABAWS.html [moved from help/html/webServices/JABAWS.html with 100% similarity]
help/help/html/webServices/RNAalifold.html [moved from help/html/webServices/RNAalifold.html with 100% similarity]
help/help/html/webServices/RNAalifoldAnnotationRows.png [moved from help/html/webServices/RNAalifoldAnnotationRows.png with 100% similarity]
help/help/html/webServices/clwqueued.gif [moved from help/html/webServices/clwqueued.gif with 100% similarity]
help/help/html/webServices/dbreffetcher.html [moved from help/html/webServices/dbreffetcher.html with 100% similarity]
help/help/html/webServices/index.html [moved from help/html/webServices/index.html with 100% similarity]
help/help/html/webServices/invalidurldialog.gif [moved from help/html/webServices/invalidurldialog.gif with 100% similarity]
help/help/html/webServices/jalviewrssreader.gif [moved from help/html/webServices/jalviewrssreader.gif with 100% similarity]
help/help/html/webServices/jnet.html [moved from help/html/webServices/jnet.html with 100% similarity]
help/help/html/webServices/jnetprediction.gif [moved from help/html/webServices/jnetprediction.gif with 100% similarity]
help/help/html/webServices/msaclient.html [moved from help/html/webServices/msaclient.html with 100% similarity]
help/help/html/webServices/multimafftjbs.gif [moved from help/html/webServices/multimafftjbs.gif with 100% similarity]
help/help/html/webServices/newsreader.html [moved from help/html/webServices/newsreader.html with 100% similarity]
help/help/html/webServices/proteinDisorder.html [moved from help/html/webServices/proteinDisorder.html with 100% similarity]
help/help/html/webServices/shmr.html [moved from help/html/webServices/shmr.html with 100% similarity]
help/help/html/webServices/urllinks.html [moved from help/html/webServices/urllinks.html with 100% similarity]
help/help/html/webServices/webServicesParams.html [moved from help/html/webServices/webServicesParams.html with 100% similarity]
help/help/html/webServices/webServicesPrefs.html [moved from help/html/webServices/webServicesPrefs.html with 100% similarity]
help/help/html/webServices/wsparams.gif [moved from help/html/webServices/wsparams.gif with 100% similarity]
help/help/html/webServices/wsprefs.gif [moved from help/html/webServices/wsprefs.gif with 100% similarity]
help/help/html/whatsNew.html [new file with mode: 0755]
help/help/icons/Home.png [moved from help/icons/Home.png with 100% similarity]
help/help/icons/back.png [moved from help/icons/back.png with 100% similarity]
help/help/icons/forward.png [moved from help/icons/forward.png with 100% similarity]
help/help/icons/print.png [moved from help/icons/print.png with 100% similarity]
help/help/icons/setup.png [moved from help/icons/setup.png with 100% similarity]
help/html/whatsNew.html [deleted file]
j11lib/FastInfoset.jar [new file with mode: 0644]
j11lib/JGoogleAnalytics_0.3.jar [new file with mode: 0644]
j11lib/Jmol-14.6.4_2016.10.26-no_netscape.jar [new file with mode: 0644]
j11lib/VARNAv3-93.jar [new file with mode: 0644]
j11lib/VAqua5-patch.jar [new file with mode: 0644]
j11lib/apache-mime4j-0.6.jar [new file with mode: 0644]
j11lib/axis.jar [new file with mode: 0755]
j11lib/biojava-core-4.1.0.jar [new file with mode: 0644]
j11lib/biojava-ontology-4.1.0.jar [new file with mode: 0644]
j11lib/commons-codec-1.3.jar [new file with mode: 0644]
j11lib/commons-compress-1.18.jar [new file with mode: 0644]
j11lib/commons-discovery.jar [new file with mode: 0755]
j11lib/commons-logging-1.1.1.jar [new file with mode: 0644]
j11lib/getdown-core.jar [new file with mode: 0644]
j11lib/gmbal-api-only-MODULE.jar [new file with mode: 0644]
j11lib/groovy-2.5.6.jar [new file with mode: 0644]
j11lib/groovy-console-2.5.6.jar [new file with mode: 0644]
j11lib/htsjdk-2.12.0.jar [new file with mode: 0644]
j11lib/httpclient-4.0.3.jar [new file with mode: 0644]
j11lib/httpcore-4.0.1.jar [new file with mode: 0644]
j11lib/httpmime-4.0.3.jar [new file with mode: 0644]
j11lib/i4jruntime.jar [new file with mode: 0644]
j11lib/intervalstore-v1.0.jar [moved from lib/intervalstore-v0.4.jar with 100% similarity]
j11lib/istack-commons-runtime.jar [new file with mode: 0644]
j11lib/jabaws-min-client-2.2.0.jar [new file with mode: 0644]
j11lib/java-json.jar [new file with mode: 0755]
j11lib/javax.activation-MODULE.jar [new file with mode: 0644]
j11lib/javax.annotation-api-MODULE.jar [new file with mode: 0644]
j11lib/javax.jws-api-1.1.jar [new file with mode: 0644]
j11lib/javax.servlet-api-MODULE.jar [new file with mode: 0644]
j11lib/javax.xml.rpc-api-1.1.2.jar [new file with mode: 0644]
j11lib/javax.xml.soap-api.jar [new file with mode: 0644]
j11lib/jaxb-api-java9.jar [new file with mode: 0644]
j11lib/jaxb-runtime.jar [new file with mode: 0644]
j11lib/jaxws-api.jar [new file with mode: 0644]
j11lib/jaxws-rt-java9.jar [new file with mode: 0644]
j11lib/jersey-client-1.19.1.jar [new file with mode: 0644]
j11lib/jersey-core-1.19.1.jar [new file with mode: 0644]
j11lib/jersey-json-1.19.1.jar [new file with mode: 0644]
j11lib/jetty-http-9.2.10.v20150310.jar [new file with mode: 0644]
j11lib/jetty-io-9.2.10.v20150310.jar [new file with mode: 0644]
j11lib/jetty-server-9.2.10.v20150310.jar [new file with mode: 0644]
j11lib/jetty-util-9.2.10.v20150310.jar [new file with mode: 0644]
j11lib/jfreesvg-2.1.jar [new file with mode: 0644]
j11lib/jhall.jar [new file with mode: 0755]
j11lib/json_simple-1.1.jar [new file with mode: 0644]
j11lib/jsoup-1.8.1.jar [new file with mode: 0644]
j11lib/jsr311-api-1.1.1.jar [moved from lib/jsr311-api-1.1.1.jar with 100% similarity]
j11lib/jswingreader-0.3.jar [new file with mode: 0644]
j11lib/libquaqua-8.0.jnilib.jar [new file with mode: 0644]
j11lib/libquaqua64-8.0.jnilib.jar [new file with mode: 0644]
j11lib/log4j-to-slf4j-2.0-rc2.jar [new file with mode: 0644]
j11lib/mail-MODULE.jar [new file with mode: 0644]
j11lib/miglayout-4.0-swing.jar [new file with mode: 0644]
j11lib/mimepull.jar [new file with mode: 0644]
j11lib/policy.jar [new file with mode: 0644]
j11lib/quaqua-filechooser-only-8.0.jar [new file with mode: 0644]
j11lib/regex.jar [new file with mode: 0755]
j11lib/saaj-impl.jar [new file with mode: 0644]
j11lib/slf4j-api-1.7.7.jar [new file with mode: 0644]
j11lib/slf4j-log4j12-1.7.7.jar [new file with mode: 0644]
j11lib/stax-ex.jar [new file with mode: 0644]
j11lib/stax2-api-MODULE.jar [new file with mode: 0644]
j11lib/streambuffer.jar [new file with mode: 0644]
j11lib/txw2.jar [new file with mode: 0644]
j11lib/vamsas-client.jar [new file with mode: 0644]
j11lib/wsdl4j-MODULE.jar [new file with mode: 0644]
j11lib/xercesImpl.jar [new file with mode: 0644]
j11mod/FastInfoset.jar [new file with mode: 0644]
j11mod/getdown-launcher.jar [new file with mode: 0644]
j11mod/istack-commons-runtime.jar [new file with mode: 0644]
j11mod/javax.activation-MODULE.jar [new file with mode: 0644]
j11mod/javax.annotation-api-MODULE.jar [new file with mode: 0644]
j11mod/javax.jws-api-1.1.jar [new file with mode: 0644]
j11mod/javax.servlet-api-MODULE.jar [new file with mode: 0644]
j11mod/javax.ws.rs-api-2.1.1.jar [new file with mode: 0644]
j11mod/javax.xml.rpc-api-1.1.2.jar [new file with mode: 0644]
j11mod/javax.xml.soap-api.jar [new file with mode: 0644]
j11mod/jaxb-api-java9.jar [new file with mode: 0644]
j11mod/jaxb-runtime.jar [new file with mode: 0644]
j11mod/jaxws-api.jar [new file with mode: 0644]
j11mod/mimepull.jar [new file with mode: 0644]
j11mod/policy.jar [new file with mode: 0644]
j11mod/saaj-impl.jar [new file with mode: 0644]
j11mod/stax-ex.jar [new file with mode: 0644]
j11mod/stax2-api-MODULE.jar [new file with mode: 0644]
j11mod/streambuffer.jar [new file with mode: 0644]
j11mod/txw2.jar [new file with mode: 0644]
j11mod/wsdl4j-MODULE.jar [new file with mode: 0644]
j8lib/JGoogleAnalytics_0.3.jar [new file with mode: 0644]
j8lib/Jmol-14.6.4_2016.10.26.jar [moved from lib/Jmol-14.6.4_2016.10.26.jar with 100% similarity]
j8lib/VARNAv3-93.jar [new file with mode: 0644]
j8lib/VAqua5-patch.jar [new file with mode: 0644]
j8lib/activation.jar [moved from lib/activation.jar with 100% similarity]
j8lib/apache-mime4j-0.6.jar [new file with mode: 0644]
j8lib/axis.jar [new file with mode: 0755]
j8lib/biojava-core-4.1.0.jar [new file with mode: 0644]
j8lib/biojava-ontology-4.1.0.jar [new file with mode: 0644]
j8lib/commons-codec-1.3.jar [new file with mode: 0644]
j8lib/commons-compress-1.18.jar [new file with mode: 0644]
j8lib/commons-discovery.jar [new file with mode: 0755]
j8lib/commons-logging-1.1.1.jar [new file with mode: 0644]
j8lib/getdown-core.jar [new file with mode: 0644]
j8lib/groovy-all-2.4.12-indy.jar [moved from lib/groovy-all-2.4.12-indy.jar with 100% similarity]
j8lib/htsjdk-2.12.0.jar [new file with mode: 0644]
j8lib/httpclient-4.0.3.jar [new file with mode: 0644]
j8lib/httpcore-4.0.1.jar [new file with mode: 0644]
j8lib/httpmime-4.0.3.jar [new file with mode: 0644]
j8lib/i4jruntime.jar [new file with mode: 0644]
j8lib/intervalstore-v1.0.jar [new file with mode: 0644]
j8lib/jabaws-min-client-2.2.0.jar [new file with mode: 0644]
j8lib/java-json.jar [new file with mode: 0755]
j8lib/jaxrpc.jar [new file with mode: 0755]
j8lib/jersey-client-1.19.jar [new file with mode: 0644]
j8lib/jersey-core-1.19.jar [new file with mode: 0644]
j8lib/jersey-json-1.19.jar [new file with mode: 0644]
j8lib/jetty-http-9.2.10.v20150310.jar [new file with mode: 0644]
j8lib/jetty-io-9.2.10.v20150310.jar [new file with mode: 0644]
j8lib/jetty-server-9.2.10.v20150310.jar [new file with mode: 0644]
j8lib/jetty-util-9.2.10.v20150310.jar [new file with mode: 0644]
j8lib/jfreesvg-2.1.jar [new file with mode: 0644]
j8lib/jhall.jar [new file with mode: 0755]
j8lib/json_simple-1.1.jar [new file with mode: 0644]
j8lib/jsoup-1.8.1.jar [new file with mode: 0644]
j8lib/jsr311-api-1.1.1.jar [new file with mode: 0644]
j8lib/jswingreader-0.3.jar [new file with mode: 0644]
j8lib/libquaqua-8.0.jnilib.jar [new file with mode: 0644]
j8lib/libquaqua64-8.0.jnilib.jar [new file with mode: 0644]
j8lib/log4j-to-slf4j-2.0-rc2.jar [new file with mode: 0644]
j8lib/mail.jar [moved from lib/mail.jar with 100% similarity]
j8lib/miglayout-4.0-swing.jar [new file with mode: 0644]
j8lib/quaqua-filechooser-only-8.0.jar [new file with mode: 0644]
j8lib/regex.jar [new file with mode: 0755]
j8lib/saaj.jar [moved from lib/saaj.jar with 100% similarity]
j8lib/servlet-api-3.1.jar [moved from lib/servlet-api-3.1.jar with 100% similarity]
j8lib/slf4j-api-1.7.7.jar [new file with mode: 0644]
j8lib/slf4j-log4j12-1.7.7.jar [new file with mode: 0644]
j8lib/vamsas-client.jar [new file with mode: 0644]
j8lib/wsdl4j.jar [moved from lib/wsdl4j.jar with 100% similarity]
j8lib/xercesImpl.jar [new file with mode: 0644]
j8lib/xml-apis.jar [moved from lib/xml-apis.jar with 100% similarity]
lib/Jmol-14.6.4_2016.10.26-no_netscape.jar [new file with mode: 0644]
lib/commons-compress-1.18.jar [new file with mode: 0644]
lib/getdown-core.jar [new file with mode: 0644]
lib/gmbal-api-only-MODULE.jar [new file with mode: 0644]
lib/groovy-2.5.6.jar [new file with mode: 0644]
lib/groovy-console-2.5.6.jar [new file with mode: 0644]
lib/intervalstore-src-v0.4.jar [deleted file]
lib/intervalstore-v1.0.jar [new file with mode: 0644]
lib/jaxws-rt-java9.jar [new file with mode: 0644]
lib/mail-MODULE.jar [new file with mode: 0644]
lib/saaj-impl.jar [new file with mode: 0644]
modules [new file with mode: 0644]
resources/images/jalview_logo_background_fade-640x480.png [new file with mode: 0644]
resources/images/jalview_logo_background_getdown-640x480.png [new file with mode: 0644]
resources/images/jalview_logo_background_getdown-progress-TEST2.png [new file with mode: 0755]
resources/images/jalview_logo_background_getdown-progress.png [new file with mode: 0644]
resources/images/jalview_logo_background_getdown-progress1.png [new file with mode: 0644]
resources/images/jalview_logo_background_getdown-progress2.png [new file with mode: 0644]
resources/images/jalview_logos.icns [new file with mode: 0755]
resources/images/jalview_logos.ico [new file with mode: 0644]
resources/images/jetset_jalview_splash.gif [new file with mode: 0644]
resources/images/jetset_jalview_splash.png [new file with mode: 0644]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/MCview/AppletPDBCanvas.java
src/MCview/Atom.java
src/MCview/PDBCanvas.java
src/com/stevesoft/pat/RegOpt.java
src/com/stevesoft/pat/Regex.java
src/com/stevesoft/pat/RegexReader.java
src/com/stevesoft/pat/RegexTokenizer.java
src/ext/edu/ucsf/rbvi/strucviz2/ChimUtils.java
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraChain.java
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/Conservation.java
src/jalview/analysis/GeneticCodes.java
src/jalview/analysis/ParseProperties.java
src/jalview/analysis/SeqsetUtils.java
src/jalview/analysis/StructureFrequency.java
src/jalview/analysis/scoremodels/FeatureDistanceModel.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AnnotationColourChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/EmbmenuFrame.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/FontChooser.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/TreePanel.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/bin/JalviewTaskbar.java [new file with mode: 0644]
src/jalview/bin/Launcher.java [new file with mode: 0644]
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/BinarySequence.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/ext/ensembl/EnsemblMap.java
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/ext/ensembl/EnsemblSequenceFetcher.java
src/jalview/ext/htsjdk/VCFReader.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/paradise/Annotate3D.java
src/jalview/gui/APQHandlers.java [new file with mode: 0644]
src/jalview/gui/AlignFrame.java
src/jalview/gui/AppJmol.java
src/jalview/gui/BlogReader.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ColourMenuHelper.java
src/jalview/gui/CutAndPasteHtmlTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/RotatableCanvas.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceRenderer.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/WsPreferences.java
src/jalview/io/AnnotationFile.java
src/jalview/io/FeaturesFile.java
src/jalview/io/FileLoader.java
src/jalview/io/JPredFile.java
src/jalview/io/JnetAnnotationMaker.java
src/jalview/io/ModellerDescription.java
src/jalview/io/NewickFile.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/vamsas/DatastoreRegistry.java
src/jalview/io/vamsas/Rangetype.java
src/jalview/io/vamsas/Sequencefeature.java
src/jalview/io/vamsas/Tree.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GCutAndPasteHtmlTransfer.java
src/jalview/jbgui/GCutAndPasteTransfer.java
src/jalview/jbgui/GDesktop.java
src/jalview/project/Jalview2XML.java
src/jalview/schemes/ColourSchemes.java
src/jalview/schemes/Consensus.java
src/jalview/schemes/FeatureColour.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/urls/CustomUrlProvider.java
src/jalview/util/ImageMaker.java
src/jalview/util/Platform.java
src/jalview/util/ShortcutKeyMaskExWrapper.java [new file with mode: 0644]
src/jalview/util/UrlLink.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/SequenceFetcher.java
src/jalview/ws/jws2/RNAalifoldClient.java
src/jalview/ws/jws2/jabaws2/Jws2Instance.java
src/jalview/ws/rest/HttpResultSet.java
src/jalview/xml/binding/jalview/JalviewModelType.java [new file with mode: 0644]
src/org/jibble/epsgraphics/EpsGraphics2D.java
src/uk/ac/ebi/www/InputParams.java
src/uk/ac/ebi/www/picr/AccessionMappingService/AccessionMapperBindingStub.java
test/jalview/bin/CommandLineOperations.java
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/SequenceFeatureTest.java
test/jalview/gui/ColourMenuHelperTest.java
test/jalview/gui/PopupMenuTest.java
test/jalview/gui/ScalePanelTest.java
test/jalview/gui/SeqPanelTest.java
test/jalview/io/IdentifyFileTest.java
test/jalview/io/StockholmFileTest.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/util/PlatformTest.java
test/jalview/util/UrlLinkTest.java
utils/InstallAnywhere/Jalview.iap_xml
utils/de-multi-release-jar.sh [new file with mode: 0755]
utils/deprecation_auto_fixes.sh [new file with mode: 0644]
utils/dev_macos_install.sh [new file with mode: 0755]
utils/getJavaVersion.java
utils/install4j/DS_Store [new file with mode: 0644]
utils/install4j/Info_plist_file_associations.xml [new file with mode: 0644]
utils/install4j/Jalview-File.icns [new file with mode: 0644]
utils/install4j/Jalview-File.ico [new file with mode: 0644]
utils/install4j/Jalview-Version-Locator.icns [new file with mode: 0644]
utils/install4j/Jalview-Version-Locator.ico [new file with mode: 0644]
utils/install4j/Jalview-Version-Locator.png [new file with mode: 0644]
utils/install4j/JalviewVersionLocator.png [new file with mode: 0644]
utils/install4j/auto_file_associations.pl [new file with mode: 0755]
utils/install4j/bs_install4j_template.install4j [new file with mode: 0644]
utils/install4j/file_associations_auto-Info_plist.xml [new file with mode: 0644]
utils/install4j/file_associations_auto-install4j.xml [new file with mode: 0644]
utils/install4j/file_associations_template-Info_plist.xml [new file with mode: 0644]
utils/install4j/file_associations_template-install4j.xml [new file with mode: 0644]
utils/install4j/install4j_template.install4j [new file with mode: 0644]
utils/install4j/jalview_dmg_background.png [new file with mode: 0644]
utils/install4j/jalview_getdown.install4j [new file with mode: 0644]
utils/jdeps_jlink_all.sh [new file with mode: 0755]
utils/modulify-jar.sh [new file with mode: 0755]
utils/showJVMVersion.java [new file with mode: 0644]

diff --git a/.classpath b/.classpath
deleted file mode 100644 (file)
index 4f9cb8a..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="src" path="utils"/>
-       <classpathentry kind="src" path="test"/>
-       <classpathentry kind="lib" path="lib/activation.jar"/>
-       <classpathentry kind="lib" path="lib/axis.jar" sourcepath="D:/axis-1_2RC2-src/axis-1_2RC2"/>
-       <classpathentry kind="lib" path="lib/commons-discovery.jar"/>
-       <classpathentry kind="lib" path="lib/jaxrpc.jar"/>
-       <classpathentry kind="lib" path="lib/jhall.jar"/>
-       <classpathentry kind="lib" path="lib/mail.jar"/>
-       <classpathentry kind="lib" path="lib/regex.jar"/>
-       <classpathentry kind="lib" path="lib/saaj.jar"/>
-       <classpathentry kind="lib" path="lib/wsdl4j.jar"/>
-       <classpathentry kind="lib" path="lib/xercesImpl.jar"/>
-       <classpathentry kind="lib" path="lib/JGoogleAnalytics_0.3.jar" sourcepath="/JGoogleAnalytics/src/main/java"/>
-       <classpathentry kind="lib" path="lib/vamsas-client.jar"/>
-       <classpathentry kind="lib" path="lib/commons-logging-1.1.1.jar"/>
-       <classpathentry kind="lib" path="lib/apache-mime4j-0.6.jar" sourcepath="G:/InstallsDir/Sources for Development/apache-mime4j-0.6-src.zip"/>
-       <classpathentry kind="lib" path="lib/httpclient-4.0.3.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/httpcore-4.0.1.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/httpmime-4.0.3.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/miglayout-4.0-swing.jar"/>
-       <classpathentry kind="lib" path="lib/jswingreader-0.3.jar" sourcepath="/jswingreader"/>
-       <classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
-       <classpathentry kind="lib" path="lib/jabaws-min-client-2.2.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-93.jar"/>
-       <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
-       <classpathentry kind="lib" path="lib/quaqua-filechooser-only-8.0.jar"/>
-       <classpathentry kind="lib" path="lib/VAqua5-patch.jar"/>
-       <classpathentry kind="lib" path="utils/classgraph-4.1.6.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin"/>
-       <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.USER_LIBRARY/Plugin.jar"/>
-       <classpathentry kind="lib" path="lib/jersey-client-1.19.jar"/>
-       <classpathentry kind="lib" path="lib/jersey-core-1.19.jar"/>
-       <classpathentry kind="lib" path="lib/jsr311-api-1.1.1.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-server-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/servlet-api-3.1.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-util-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-http-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-io-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/java-json.jar"/>
-       <classpathentry kind="lib" path="lib/Jmol-14.6.4_2016.10.26.jar"/>
-       <classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
-       <classpathentry kind="lib" path="lib/biojava-core-4.1.0.jar"/>
-       <classpathentry kind="lib" path="lib/biojava-ontology-4.1.0.jar"/>
-       <classpathentry kind="lib" path="lib/htsjdk-2.12.0.jar"/>
-       <classpathentry kind="lib" path="lib/groovy-all-2.4.12-indy.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
-       <classpathentry kind="lib" path="lib/intervalstore-v0.4.jar"/>
-       <classpathentry kind="output" path="classes"/>
-</classpath>
index 211ddc9..a2a64e7 100644 (file)
@@ -1,5 +1,6 @@
 /*.project
-.classpath
+/.classpath
+/.settings/org.eclipse.jdt.core.prefs
 /dist
 /clover
 /classes
@@ -7,7 +8,6 @@
 /test-reports
 /test-output
 .externalToolBuilders/*
-.settings/*
 /.DS_Store
 .DS_Store
 /.com.apple.timemachine.supported
@@ -17,3 +17,15 @@ TESTNG
 /benchmarking/lib
 *.class
 /site
+/.gradle
+/build
+/utils/HelpLinksChecker.out
+/getdown/website
+/getdown/full_app
+/getdown/files
+/getdown/src/getdown/*/target/
+/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java
+/getdown/src/getdown/launcher/dependency-reduced-pom.xml
+/utils/install4j/jalview-installers-*.install4j
+*.swp
+/bin
diff --git a/.project b/.project
deleted file mode 100644 (file)
index d0dfc7e..0000000
--- a/.project
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>Jalview Release 2.7</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.wst.common.project.facet.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
-                       <triggers>full,incremental,</triggers>
-                       <arguments>
-                               <dictionary>
-                                       <key>LaunchConfigHandle</key>
-                                       <value>&lt;project&gt;/.externalToolBuilders/Jalview Release indices [Builder].launch</value>
-                               </dictionary>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
-               <nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-               <nature>org.eclipse.wst.common.project.facet.core.nature</nature>
-               <nature>de.tud.st.ispace.builder.ISpaceNature</nature>
-               <nature>org.eclipse.jem.beaninfo.BeanInfoNature</nature>
-       </natures>
-</projectDescription>
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
new file mode 100644 (file)
index 0000000..e889521
--- /dev/null
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
similarity index 99%
rename from .settings/org.eclipse.jdt.core.prefs
rename to .settings/org.eclipse.jdt.core.jalview.prefs
index 5908bb2..4b7c545 100644 (file)
@@ -1,15 +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.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=11
 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.8
+org.eclipse.jdt.core.compiler.source=11
 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=52
@@ -294,3 +294,5 @@ org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
 org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
 org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+
diff --git a/.settings/org.eclipse.jdt.groovy.core.prefs b/.settings/org.eclipse.jdt.groovy.core.prefs
new file mode 100644 (file)
index 0000000..74af1ba
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+groovy.compiler.level=25
index 958613b..41487fe 100644 (file)
@@ -1,7 +1,7 @@
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_Jalview
-formatter_settings_version=12
+formatter_settings_version=16
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=jalview;java;javax;org;com;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/JAVA-11-README b/JAVA-11-README
new file mode 100644 (file)
index 0000000..805a4c1
--- /dev/null
@@ -0,0 +1,94 @@
+Java 11 module replacements
+
+As Java 11 no longer ships with Java EE libraries, which were standard in Java 8, and available (modularised, through the --add-modules CLI mechanism) in Java 9 and 10, third party replacement libraries for these libraries/modules have to be found.
+
+Ideally, these jar files would be packaged as modules (i.e. would contains a module-info.class with the relevant provision and requirements information for that module package).  However, as at time of writing (end Nov 2018) the available packages are not fully mature.
+There also seems to be no single place to look when trying to obtain modules jar files.
+
+A couple of good places to start:
+https://javaee.github.io/
+(see Metro, Glassfish etc)
+
+https://search.maven.org/
+(search for e.g. a:javax.activation)
+
+https://mvnrepository.com/
+(if the other two fail!)
+
+Unfortunately there seems to be multiple projects providing the same modules, with little indication of which is "best".
+Here's an example of what to do with search.maven.org, looking for, say, the java.activation module
+
+Firstly, remember this might be called javax.activation (in fact it is), so searching for
+
+a:javax.activation
+
+shows 3 possible candidates.
+
+The "Updated" date gives a clue if a library might be up-to-date or still being maintained, and of the three listed, com.sun.activation version 1.2.0 was updated in Sep 2017, and is the only one updated in the last 5 years, so it looks like a good candidate.
+Click on the arrow in the Download column which gives various download options.  Choose "jar" and this should download straight away.
+
+If you have a peek inside the jar file:
+
+$ jar -tvf ~/Downloads/javax.activation-1.2.0.jar
+
+     0 Wed Sep 06 16:13:08 BST 2017 META-INF/
+  1307 Wed Sep 06 16:13:06 BST 2017 META-INF/MANIFEST.MF
+     0 Wed Sep 06 14:23:50 BST 2017 javax/
+     0 Wed Sep 06 14:23:50 BST 2017 javax/activation/
+     0 Wed Sep 06 14:23:50 BST 2017 com/
+     0 Wed Sep 06 14:23:50 BST 2017 com/sun/
+     0 Wed Sep 06 14:23:50 BST 2017 com/sun/activation/
+...
+  2238 Wed Sep 06 14:23:50 BST 2017 com/sun/activation/registries/LineTokenizer.class
+ 39394 Wed Sep 06 16:13:06 BST 2017 META-INF/LICENSE.txt
+   581 Wed Sep 06 16:13:06 BST 2017 META-INF/mimetypes.default
+   292 Wed Sep 06 16:13:06 BST 2017 META-INF/mailcap.default
+     0 Wed Sep 06 16:13:08 BST 2017 META-INF/maven/
+     0 Wed Sep 06 16:13:08 BST 2017 META-INF/maven/com.sun.activation/
+     0 Wed Sep 06 16:13:08 BST 2017 META-INF/maven/com.sun.activation/javax.activation/
+  6515 Fri Sep 01 16:13:04 BST 2017 META-INF/maven/com.sun.activation/javax.activation/pom.xml
+   119 Wed Sep 06 14:23:52 BST 2017 META-INF/maven/com.sun.activation/javax.activation/pom.properties
+
+
+you can see that it doesn't have a module-info.class file, meaning it can only be used as an "automatic" module.  This will probably mean it can't be used with jlink when creating a JRE.
+HAVE NO FEAR!
+We can semi-manually (urgh!) create a module-info.class for this library and turn it into a module.  This really should be scripted. I might just do it.
+
+So, assuming we downloaded that jar to ~/Downloads/javax.activation-1.2.0.jar, and JAVA_HOME is set to a java distribution, we can do:
+
+mkdir tmp
+cd tmp
+jar -xvf ~/Downloads/javax.activation-1.2.0.jar
+jdeps --generate-module-info . ~/Downloads/javax.activation-1.2.0.jar
+# --generate-module-info creates the module-info.java file we are looking for! but for some reason insists on sticking it in a java.activation subdir (even though we ask for '.')
+mv java.activation/module-info.java .
+rmdir java.activation
+javac -d . module-info.java
+# we could clean up (rm) module-info.java, but I think it'll be more useful to keep a hold of it in the jar, it's not causing a problem or taking up space
+cd ..
+jar -cvf javax.activation-1.2.0-MODULE.jar -C ./tmp .
+/bin/rm -rf ./tmp
+
+and voila, you have a modulified version of the library in the jar file ./javax.activation-1.2.0-MODULE.jar.  This can be used with jlink.
+[ timeout: I scripted this as utils/modulify.sh  Usage: modulify.sh /path/to/jarfile.jar  ... creates /path/to/jarfile-MODULE.jar ]
+
+Once we have enough modulified jar files (note, we kind of keep downloading a new file or files until jalview starts without Exceptions, jdeps could probably provide a better way(!), and jaxws-rt provides quite a few of the needed jar files, hopefully in a coherent way).
+
+A list of module dependencies can be found with (note module-path doesn't need jar files, just the dirs to look in, unlike class-path)
+
+jdeps --class-path="lib/*:j11lib/*" --module-path="$JAVA_HOME/jmods:j11lib" --list-deps dist/jalview.jar libs/*.jar
+
+will end with a list of modules required in the JRE, these need to be comma-separated-listed for jlink.  We /ought/ to be able to do this by using "--print-module-deps" like this
+
+jdeps --class-path="lib/*:j11lib/*" --module-path="$JAVA_HOME/jmods:j11lib" --print-module-deps dist/jalview.jar libs/*.jar
+
+but that ends with an Exception.  Perhaps should look into that...
+
+Anyway, with the list of modules required (in the file "modules") you can do
+
+jlink --module-path $JAVA_HOME/jmods:j11lib --compress=2 --add-modules `cat modules` --no-header-files --no-man-pages --bind-services --output j11jre/openjdk11_platform
+
+To create a Java 11 JRE in j11jre/openjdk11_platform (or whatever you wish to call it).
+You can point JAVA_HOME at the JDK11 of a different platform, so long as the jlink in your path is to the jlink for the platform you're running on.
+
+
diff --git a/authors.props b/authors.props
new file mode 100644 (file)
index 0000000..3c06708
--- /dev/null
@@ -0,0 +1,4 @@
+YEAR=2018
+AUTHORS=J Procter, M Carstairs, B Soares, K Mourao, TC Ofoegbu, AM Waterhouse, J Engelhardt, LM Lui, A Menard, D Barton, N Sherstnev, D Roldan-Martinez, M Clamp, S Searle, G Barton
+AUTHORFNAMES=Jim Procter, Mungo Carstairs, Ben Soares, Kira Mourao, Tochukwu 'Charles' Ofoegbu, Andrew Waterhouse, Jan Engelhardt, Lauren Lui, Anne Menard, Daniel Barton, Natasha Sherstnev, David Roldan-Martinez, Michele Clamp, James Cuff, Steve Searle, David Martin & Geoff Barton
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644 (file)
index 0000000..07a4095
--- /dev/null
@@ -0,0 +1,886 @@
+import org.apache.tools.ant.filters.ReplaceTokens
+//import org.apache.tools.ant.filters.ReplaceRegexp
+import org.gradle.internal.os.OperatingSystem
+
+buildscript {
+  dependencies {
+    classpath 'org.openclover:clover:4.3.1'
+    classpath 'org.apache.commons:commons-compress:1.18'
+  }
+}
+
+plugins {
+  id 'java'
+  id 'application'
+  id 'eclipse'
+  id 'com.github.johnrengelman.shadow' version '4.0.3'
+  id 'com.install4j.gradle' version '7.0.9'
+}
+
+repositories {
+  jcenter()
+  mavenCentral()
+  mavenLocal()
+  flatDir {
+    dirs gradlePluginsDir
+  }
+}
+
+mainClassName = launcherClass
+def cloverInstrDir = file("$buildDir/$cloverSourcesInstrDir")
+def classes = "$jalviewDir/$classesDir"
+
+if (clover.equals("true")) {
+  use_clover = true
+  classes = "$buildDir/$cloverClassesDir"
+} else {
+  use_clover = false
+  classes = "$jalviewDir/$classesDir"
+}
+
+// configure classpath/args for j8/j11 compilation
+
+def jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
+def libDir
+def libDistDir
+def compile_source_compatibility
+def compile_target_compatibility
+
+ext {
+  // where the getdown channel will be built.
+  // TODO: consider allowing this expression to  be overrriden by -P arg
+  getdownWebsiteDir = jalviewDir + '/' + getdown_website_dir + '/' + JAVA_VERSION + '/'
+  getdownAppDir = getdownWebsiteDir + '/' + getdown_app_dir
+  getdownJ11libDir = getdownWebsiteDir + '/' + getdown_j11lib_dir
+  getdownResourceDir = getdownWebsiteDir + '/' + getdown_resource_dir
+  getdownLauncher = jalviewDir + '/' + getdown_launcher
+  getdownFilesDir = jalviewDir + '/' + getdown_files_dir + '/' + JAVA_VERSION + '/'
+  def getdownChannel = getdown_channel_name
+  if (getdown_channel_name.equals("COMMIT")) {
+    getdownChannel = getGitHash()
+  }
+  getdown_app_base = getdown_channel_base+"/"+getdownChannel+"/"+JAVA_VERSION+"/"
+  modules_compileClasspath = fileTree(dir: "$jalviewDir/$j11modDir", include: ["*.jar"])
+  modules_runtimeClasspath = modules_compileClasspath
+}
+
+def JAVA_INTEGER_VERSION
+def additional_compiler_args = []
+// these are getdown.txt properties defined dependent on the JAVA_VERSION
+def getdown_alt_java_min_version
+// this property is assigned below and expanded to multiple lines in the getdown task
+def getdown_alt_multi_java_location
+if (JAVA_VERSION.equals("1.8")) {
+  JAVA_INTEGER_VERSION = "8"
+  libDir = j11libDir
+  libDistDir = j8libDir
+  compile_source_compatibility = 1.8
+  compile_target_compatibility = 1.8
+  getdown_alt_java_min_version = getdown_alt_java8_min_version
+  getdown_alt_multi_java_location = getdown_alt_java8_txt_multi_java_location
+} else if (JAVA_VERSION.equals("11")) {
+  JAVA_INTEGER_VERSION = "11"
+  libDir = j11libDir
+  libDistDir = j11libDir
+  compile_source_compatibility = 11
+  compile_target_compatibility = 11
+  getdown_alt_java_min_version = getdown_alt_java11_min_version
+  getdown_alt_multi_java_location = getdown_alt_java11_txt_multi_java_location
+  additional_compiler_args += [
+    '--module-path', ext.modules_compileClasspath.asPath,
+    '--add-modules', j11modules
+  ]
+} else {
+  throw new GradleException("JAVA_VERSION=$JAVA_VERSION not currently supported by Jalview")
+}
+
+sourceSets {
+
+  main {
+    java {
+      srcDirs "$jalviewDir/$sourceDir"
+      outputDir = file("$classes")
+    }
+
+    resources {
+      srcDirs "$jalviewDir/$resourceDir"
+      srcDirs "$jalviewDir/$libDistDir"
+    }
+
+    jar.destinationDir = file("$jalviewDir/$packageDir")
+
+    compileClasspath = files(sourceSets.main.java.outputDir)
+    compileClasspath += fileTree(dir: "$jalviewDir/$libDir", include: ["*.jar"])
+
+    runtimeClasspath = compileClasspath
+  }
+  clover {
+    java {
+      srcDirs = [ cloverInstrDir ]
+      outputDir = file("${buildDir}/${cloverClassesDir}")
+    }
+
+    resources {
+      srcDirs = sourceSets.main.resources.srcDirs
+    }
+    compileClasspath = configurations.cloverRuntime + files( sourceSets.clover.java.outputDir )
+    compileClasspath += files(sourceSets.main.java.outputDir)
+    compileClasspath += sourceSets.main.compileClasspath
+    compileClasspath += fileTree(dir: "$jalviewDir/$utilsDir", include: ["**/*.jar"])
+    compileClasspath += fileTree(dir: "$jalviewDir/$libDir", include: ["*.jar"])
+
+    runtimeClasspath = compileClasspath
+  }
+
+  test {
+    java {
+      srcDirs "$jalviewDir/$testSourceDir"
+      outputDir = file("$jalviewDir/$testOutputDir")
+    }
+
+    resources {
+      srcDirs = sourceSets.main.resources.srcDirs
+    }
+
+    compileClasspath = files( sourceSets.test.java.outputDir )
+
+    if (use_clover) {
+      compileClasspath += sourceSets.clover.compileClasspath
+    } else {
+      compileClasspath += files(sourceSets.main.java.outputDir)
+    }
+    compileClasspath += sourceSets.main.compileClasspath
+    compileClasspath += files( sourceSets.main.resources.srcDirs)
+    compileClasspath += fileTree(dir: "$jalviewDir/$utilsDir", include: ["**/*.jar"])
+    compileClasspath += fileTree(dir: "$jalviewDir/$libDir", include: ["*.jar"])
+
+    runtimeClasspath = compileClasspath
+  }
+}
+
+// clover bits
+dependencies {
+  if (use_clover) {
+    cloverCompile 'org.openclover:clover:4.3.1'
+    testCompile 'org.openclover:clover:4.3.1'
+  }
+}
+
+configurations {
+  cloverRuntime
+  cloverRuntime.extendsFrom cloverCompile
+}
+
+eclipse {
+  project {
+    name = "Jalview with gradle build"
+
+    natures 'org.eclipse.jdt.core.javanature',
+        'org.eclipse.jdt.groovy.core.groovyNature',
+        'org.eclipse.buildship.core.gradleprojectnature'
+
+    buildCommand 'org.eclipse.jdt.core.javabuilder'
+    buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
+  }
+
+  classpath {
+    //defaultOutputDir = sourceSets.main.java.outputDir
+    def removeThese = []
+    configurations.each{ if (it.isCanBeResolved()) {
+        removeThese += it
+      }
+    }
+    containers 'org.eclipse.buildship.core.gradleclasspathcontainer'
+
+    minusConfigurations += removeThese
+    plusConfigurations = [ ]
+    file {
+
+      whenMerged { cp ->
+        def removeTheseToo = []
+        HashMap<String, Boolean> addedSrcPath = new HashMap<>();
+        cp.entries.each { entry ->
+          if (entry.kind == 'src') {
+            if (addedSrcPath.getAt(entry.path) || !(entry.path == "src" || entry.path == "test")) {
+              removeTheseToo += entry
+            } else {
+              addedSrcPath.putAt(entry.path, true)
+            }
+          }
+        }
+        cp.entries.removeAll(removeTheseToo)
+      }
+
+      withXml {
+        def node = it.asNode()
+        def srcTestAttributes
+        node.children().each{ cpe ->
+          def attributes = cpe.attributes()
+          if (attributes.get("kind") == "src" && attributes.get("path") == "test") {
+            srcTestAttributes = cpe.find { a -> a.name() == "attributes" }
+            return
+          }
+        }
+        def addTestAttribute = true
+        srcTestAttributes.each{a ->
+          if (a.name() == "attribute" && a.attributes().getAt("name") == "test") {
+            addTestAttribute = false
+          }
+        }
+        if (addTestAttribute) {
+          srcTestAttributes.append(new Node(null, "attribute", [name:"test", value:"true"]))
+        }
+
+        node.appendNode('classpathentry', [kind:"output", path:"bin/main"])
+        node.appendNode('classpathentry', [kind:"lib", path:helpParentDir])
+        node.appendNode('classpathentry', [kind:"lib", path:resourceDir])
+        HashMap<String, Boolean> addedLibPath = new HashMap<>();
+        def allPaths = sourceSets.test.compileClasspath + sourceSets.main.compileClasspath
+        sourceSets.main.compileClasspath.each{
+          //if ((it.isDirectory() || ! it.exists()) && ! (it.equals(sourceSets.main.java.outputDir))) {
+          //no longer want to add outputDir as eclipse is using its own output dir in bin/main
+          if (it.isDirectory() || ! it.exists()) {
+            // don't add dirs to classpath
+            //println("Not adding directory "+it)
+            return
+          }
+          def itPath = it.toString()
+          if (itPath.startsWith(jalviewDirAbsolutePath+"/")) {
+            itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
+          }
+          if (addedLibPath.get(itPath)) {
+            //println("Not adding duplicate entry "+itPath)
+          } else {
+            //println("Adding entry "+itPath)
+            node.appendNode('classpathentry', [kind:"lib", path:itPath])
+            addedLibPath.put(itPath, true)
+          }
+        }
+        sourceSets.test.compileClasspath.each{
+          //if ((it.isDirectory() || ! it.exists()) && ! (it.equals(sourceSets.main.java.outputDir))) {
+          //no longer want to add outputDir as eclipse is using its own output dir in bin/main
+          if (it.isDirectory() || ! it.exists()) {
+            // don't add dirs to classpath
+            //println("Not adding directory "+it)
+            return
+          }
+          def itPath = it.toString()
+          if (itPath.startsWith(jalviewDirAbsolutePath+"/")) {
+            itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
+          }
+          if (addedLibPath.get(itPath)) {
+            //println("Not adding duplicate entry "+itPath)
+          } else {
+            //println("Adding entry "+itPath)
+            node.appendNode('classpathentry', [kind:"lib", path:itPath])
+            .appendNode('attributes')
+            .appendNode('attribute', [name:"test", value:"true"])
+            addedLibPath.put(itPath, true)
+          }
+        }
+      }
+
+    }
+  }
+
+  jdt {
+    // for the IDE, use java 11 compatibility
+    sourceCompatibility = 11
+    targetCompatibility = 11
+    javaRuntimeName = "JavaSE-11"
+
+    file {
+      withProperties { props ->
+        def jalview_prefs = new Properties()
+        def ins = new FileInputStream(eclipse_extra_jdt_prefs_file)
+        jalview_prefs.load(ins)
+        ins.close()
+        jalview_prefs.forEach { t, v ->
+          if (props.getAt(t) == null) {
+            props.putAt(t, v)
+          }
+        }
+      }
+    }
+  }
+
+}
+
+task cloverInstr() {
+  // only instrument source, we build test classes as normal
+  inputs.files files (sourceSets.main.allJava) // , fileTree(dir:"$jalviewDir/$testSourceDir", include: ["**/*.java"]))
+  outputs.dir cloverInstrDir
+
+  doFirst {
+    delete cloverInstrDir
+    def argsList = ["--initstring", "${buildDir}/clover/clover.db",
+      "-d", "${buildDir}/${cloverSourcesInstrDir}"]
+    argsList.addAll(inputs.files.files.collect({ file ->
+      file.absolutePath
+    }))
+    String[] args = argsList.toArray()
+    println("About to instrument "+args.length +" files")
+    com.atlassian.clover.CloverInstr.mainImpl(args)
+  }
+}
+
+
+task cloverReport {
+  group = "Verification"
+  description = "Createst the Clover report"
+  inputs.dir "${buildDir}/clover"
+  outputs.dir "${reportsDir}/clover"
+  onlyIf {
+    file("${buildDir}/clover/clover.db").exists()
+  }
+  doFirst {
+    def argsList = ["--initstring", "${buildDir}/clover/clover.db",
+      "-o", "${reportsDir}/clover"]
+    String[] args = argsList.toArray()
+    com.atlassian.clover.reporters.html.HtmlReporter.runReport(args)
+
+    // and generate ${reportsDir}/clover/clover.xml
+    args = ["--initstring", "${buildDir}/clover/clover.db",
+      "-o", "${reportsDir}/clover/clover.xml"].toArray()
+    com.atlassian.clover.reporters.xml.XMLReporter.runReport(args)
+  }
+}
+
+// end clover bits
+
+
+compileJava {
+
+  doFirst {
+    sourceCompatibility = compile_source_compatibility
+    targetCompatibility = compile_target_compatibility
+    options.compilerArgs = additional_compiler_args
+    print ("Setting target compatibility to "+targetCompatibility+"\n")
+  }
+
+}
+
+compileTestJava {
+  if (use_clover) {
+    dependsOn compileCloverJava
+    classpath += configurations.cloverRuntime
+  } else {
+    classpath += sourceSets.main.runtimeClasspath
+  }
+  doFirst {
+    sourceCompatibility = compile_source_compatibility
+    targetCompatibility = compile_target_compatibility
+    options.compilerArgs = additional_compiler_args
+    print ("Setting target compatibility to "+targetCompatibility+"\n")
+  }
+}
+
+
+compileCloverJava {
+
+  doFirst {
+    sourceCompatibility = compile_source_compatibility
+    targetCompatibility = compile_target_compatibility
+    options.compilerArgs += additional_compiler_args
+    print ("Setting target compatibility to "+targetCompatibility+"\n")
+  }
+  classpath += configurations.cloverRuntime
+}
+
+clean {
+  delete sourceSets.main.java.outputDir
+}
+
+cleanTest {
+  delete sourceSets.test.java.outputDir
+  delete cloverInstrDir
+}
+
+def getDate(format) {
+  def date = new Date()
+  //return date.format("dd MMMM yyyy")
+  return date.format(format)
+}
+
+def getGitHash() {
+  def stdout = new ByteArrayOutputStream()
+  exec {
+    commandLine "git", "rev-parse", "--short", "HEAD"
+    standardOutput = stdout
+    workingDir = jalviewDir
+  }
+  return stdout.toString().trim()
+}
+
+def getGitBranch() {
+  def stdout = new ByteArrayOutputStream()
+  exec {
+    commandLine "git", "rev-parse", "--abbrev-ref", "HEAD"
+    standardOutput = stdout
+    workingDir = jalviewDir
+  }
+  return stdout.toString().trim()
+}
+
+task createBuildProperties(type: WriteProperties) {
+  inputs.dir("$jalviewDir/$sourceDir")
+  inputs.dir("$jalviewDir/$resourceDir")
+  outputFile "$classes/$buildPropertiesFile"
+  /* taking time/date specific comment out to allow better incremental builds */
+  //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
+  comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
+  property "BUILD_DATE", getDate("dd MMMM yyyy")
+  property "VERSION", JALVIEW_VERSION
+  property "INSTALLATION", INSTALLATION+" git-commit:"+getGitHash()+" ["+getGitBranch()+"]"
+  outputs.file(outputFile)
+  outputs.dir("$classes")
+}
+
+task syncDocs(type: Sync) {
+  def syncDir = "$classes/$docDir"
+  from fileTree("$jalviewDir/$docDir")
+  into syncDir
+
+}
+
+def helpFile = "$classes/$helpDir/help.jhm"
+
+task copyHelp(type: Copy) {
+  def inputDir = "$jalviewDir/$helpParentDir/$helpDir"
+  def outputDir = "$classes/$helpDir"
+  from(inputDir) {
+    exclude '**/*.gif'
+    exclude '**/*.jpg'
+    exclude '**/*.png'
+    filter(ReplaceTokens, beginToken: '$$', endToken: '$$', tokens: ['Version-Rel': JALVIEW_VERSION])
+  }
+  from(inputDir) {
+    include '**/*.gif'
+    include '**/*.jpg'
+    include '**/*.png'
+  }
+  into outputDir
+
+  inputs.dir(inputDir)
+  outputs.files(helpFile)
+  outputs.dir(outputDir)
+}
+
+task syncLib(type: Sync) {
+  def syncDir = "$classes/$libDistDir"
+  from fileTree("$jalviewDir/$libDistDir")
+  into syncDir
+}
+
+task syncResources(type: Sync) {
+  from "$jalviewDir/$resourceDir"
+  include "**/*.*"
+  exclude "install4j"
+  into "$classes"
+  preserve {
+    include "**"
+  }
+}
+
+task prepare {
+  dependsOn syncResources
+  dependsOn syncDocs
+  dependsOn copyHelp
+}
+
+
+//testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
+test {
+  dependsOn prepare
+  dependsOn compileJava
+  if (use_clover) {
+    dependsOn cloverInstr
+  }
+
+  print("Running tests " + (use_clover?"WITH":"WITHOUT") + " clover [clover="+use_clover+"]\n")
+
+  useTestNG() {
+    includeGroups testngGroups
+    preserveOrder true
+    useDefaultListeners=true
+  }
+
+  workingDir = jalviewDir
+  //systemProperties 'clover.jar' System.properties.clover.jar
+  sourceCompatibility = compile_source_compatibility
+  targetCompatibility = compile_target_compatibility
+  jvmArgs += additional_compiler_args
+  print ("Setting target compatibility to "+targetCompatibility+"\n")
+}
+
+task buildIndices(type: JavaExec) {
+  dependsOn copyHelp
+  classpath = sourceSets.main.compileClasspath
+  main = "com.sun.java.help.search.Indexer"
+  workingDir = "$classes/$helpDir"
+  def argDir = "html"
+  args = [ argDir ]
+  inputs.dir("$workingDir/$argDir")
+
+  outputs.dir("$classes/doc")
+  outputs.dir("$classes/help")
+  outputs.file("$workingDir/JavaHelpSearch/DOCS")
+  outputs.file("$workingDir/JavaHelpSearch/DOCS.TAB")
+  outputs.file("$workingDir/JavaHelpSearch/OFFSETS")
+  outputs.file("$workingDir/JavaHelpSearch/POSITIONS")
+  outputs.file("$workingDir/JavaHelpSearch/SCHEMA")
+  outputs.file("$workingDir/JavaHelpSearch/TMAP")
+}
+
+task compileLinkCheck(type: JavaCompile) {
+  options.fork = true
+  classpath = files("$jalviewDir/$utilsDir")
+  destinationDir = file("$jalviewDir/$utilsDir")
+  source = fileTree(dir: "$jalviewDir/$utilsDir", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
+
+  outputs.file("$jalviewDir/$utilsDir/HelpLinksChecker.class")
+  outputs.file("$jalviewDir/$utilsDir/BufferedLineReader.class")
+}
+
+def helplinkscheckeroutputfile = file("$jalviewDir/$utilsDir/HelpLinksChecker.out")
+task linkCheck(type: JavaExec) {
+  dependsOn prepare, compileLinkCheck
+  classpath = files("$jalviewDir/$utilsDir")
+  main = "HelpLinksChecker"
+  workingDir = jalviewDir
+  def help = "$classes/$helpDir"
+  args = [ "$classes/$helpDir", "-nointernet" ]
+  //args = [ "$classesDir/$helpDir", "-nointernet" ]
+
+  doFirst {
+    helplinkscheckeroutputfile.createNewFile()
+    standardOutput new FileOutputStream(helplinkscheckeroutputfile, false)
+  }
+
+  outputs.file(helplinkscheckeroutputfile)
+}
+
+task cleanPackageDir(type: Delete) {
+  delete fileTree("$jalviewDir/$packageDir").include("*.jar")
+}
+
+jar {
+  dependsOn linkCheck
+  dependsOn buildIndices
+  dependsOn createBuildProperties
+
+  manifest {
+    attributes "Main-Class": mainClass,
+    "Permissions": "all-permissions",
+    "Application-Name": "Jalview Desktop",
+    "Codebase": application_codebase
+  }
+
+  destinationDir = file("$jalviewDir/$packageDir")
+  archiveName = rootProject.name+".jar"
+
+  exclude "cache*/**"
+  exclude "*.jar"
+  exclude "*.jar.*"
+  exclude "**/*.jar"
+  exclude "**/*.jar.*"
+
+  inputs.dir("$classes")
+  outputs.file("$jalviewDir/$packageDir/$archiveName")
+}
+
+task copyJars(type: Copy) {
+  from fileTree("$classes").include("**/*.jar").include("*.jar").files
+  into "$jalviewDir/$packageDir"
+}
+
+// doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
+task syncJars(type: Sync) {
+  from fileTree("$jalviewDir/$libDistDir").include("**/*.jar").include("*.jar").files
+  into "$jalviewDir/$packageDir"
+  preserve {
+    include jar.archiveName
+  }
+}
+
+task makeDist {
+  group = "build"
+  description = "Put all required libraries in dist"
+  // order of "cleanPackageDir", "copyJars", "jar" important!
+  jar.mustRunAfter cleanPackageDir
+  syncJars.mustRunAfter cleanPackageDir
+  dependsOn cleanPackageDir
+  dependsOn syncJars
+  dependsOn jar
+  outputs.dir("$jalviewDir/$packageDir")
+}
+
+task cleanDist {
+  dependsOn cleanPackageDir
+  dependsOn cleanTest
+  dependsOn clean
+}
+
+shadowJar {
+  dependsOn makeDist
+  from ("$jalviewDir/$libDistDir") {
+    include("*.jar")
+  }
+  mainClassName = shadowJarMainClass
+  mergeServiceFiles()
+  classifier = "all-"+JAVA_VERSION
+  minimize()
+}
+
+task getdownWebsite() {
+  group = "distribution"
+  description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
+  dependsOn makeDist
+  def getdownWebsiteResourceFilenames = []
+  def getdownTextString = ""
+  def getdownResourceDir = project.ext.getdownResourceDir
+  def getdownAppDir = project.ext.getdownAppDir
+  def getdownResourceFilenames = []
+  doFirst {
+    // go through properties looking for getdown_txt_...
+    def props = project.properties.sort { it.key }
+    props.put("getdown_txt_java_min_version", getdown_alt_java_min_version)
+    props.put("getdown_txt_multi_java_location", getdown_alt_multi_java_location)
+
+    if (getdown_local == "true") {
+      getdown_app_base = "file://"+file(getdownWebsiteDir).getAbsolutePath()
+    }
+    props.put("getdown_txt_appbase", getdown_app_base)
+    props.each{ prop, val ->
+      if (prop.startsWith("getdown_txt_") && val != null) {
+        if (prop.startsWith("getdown_txt_multi_")) {
+          def key = prop.substring(18)
+          val.split(",").each{ v ->
+            def line = key + " = " + v + "\n"
+            getdownTextString += line
+          }
+        } else {
+          // file values rationalised
+          if (val.indexOf('/') > -1) {
+            def r = null
+            if (val.indexOf('/') == 0) {
+              // absolute path
+              r = file(val)
+            } else if (val.indexOf('/') > 0) {
+              // relative path (relative to jalviewDir)
+              r = file( jalviewDir + '/' + val )
+            }
+            if (r.exists()) {
+              val = getdown_resource_dir + '/' + r.getName()
+              getdownWebsiteResourceFilenames += val
+              getdownResourceFilenames += r.getPath()
+            }
+          }
+          def line = prop.substring(12) + " = " + val + "\n"
+          getdownTextString += line
+        }
+      }
+    }
+
+    getdownWebsiteResourceFilenames.each{ filename ->
+      getdownTextString += "resource = "+filename+"\n"
+    }
+    getdownResourceFilenames.each{ filename ->
+      copy {
+        from filename
+        into project.ext.getdownResourceDir
+      }
+    }
+
+    def codeFiles = []
+    makeDist.outputs.files.each{ f ->
+      if (f.isDirectory()) {
+        def files = fileTree(dir: f, include: ["*"]).getFiles()
+        codeFiles += files
+      } else if (f.exists()) {
+        codeFiles += f
+      }
+    }
+    codeFiles.sort().each{f ->
+      def line = "code = " + getdown_app_dir + '/' + f.getName() + "\n"
+      getdownTextString += line
+      copy {
+        from f.getPath()
+        into project.ext.getdownAppDir
+      }
+    }
+
+    // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
+    /*
+     if (JAVA_VERSION.equals("11")) {
+     def j11libFiles = fileTree(dir: "$jalviewDir/$j11libDir", include: ["*.jar"]).getFiles()
+     j11libFiles.sort().each{f ->
+     def line = "code = " + getdown_j11lib_dir + '/' + f.getName() + "\n"
+     getdownTextString += line
+     copy {
+     from f.getPath()
+     into project.ext.getdownJ11libDir
+     }
+     }
+     }
+     */
+
+    // getdown-launcher.jar should not be in main application class path so the main application can move it when updated.  Listed as a resource so it gets updated.
+    //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
+    getdownTextString += "resource = " + file(getdownLauncher).getName() + "\n"
+    getdownTextString += "class = " + mainClass + "\n"
+
+    def getdown_txt = file(project.ext.getdownWebsiteDir + "/getdown.txt")
+    getdown_txt.write(getdownTextString)
+
+    copy {
+      from getdown_txt
+      into project.ext.getdownFilesDir
+    }
+
+    copy {
+      from getdownLauncher
+      into project.ext.getdownFilesDir
+    }
+
+    copy {
+      from getdownLauncher
+      into project.ext.getdownWebsiteDir
+    }
+
+    copy {
+      from jalviewDir + '/' + project.getProperty('getdown_txt_ui.background_image')
+      from jalviewDir + '/' + project.getProperty('getdown_txt_ui.error_background')
+      from jalviewDir + '/' + project.getProperty('getdown_txt_ui.progress_image')
+      from jalviewDir + '/' + project.getProperty('getdown_txt_ui.icon')
+      from jalviewDir + '/' + project.getProperty('getdown_txt_ui.mac_dock_icon')
+      into project.ext.getdownFilesDir + '/' + getdown_resource_dir
+    }
+  }
+
+  inputs.dir(jalviewDir + '/' + packageDir)
+  outputs.dir(project.ext.getdownWebsiteDir)
+  outputs.dir(project.ext.getdownFilesDir)
+}
+
+task getdownDigest(type: JavaExec) {
+  group = "distribution"
+  description = "Digest the getdown website folder"
+  dependsOn getdownWebsite
+  classpath = files(jalviewDir + '/' + getdown_core, jalviewDir+'/'+getdown_launcher)
+  main = "com.threerings.getdown.tools.Digester"
+  args project.ext.getdownWebsiteDir
+  inputs.dir(project.ext.getdownWebsiteDir)
+  outputs.file(project.ext.getdownWebsiteDir + '/' + "digest2.txt")
+}
+
+task getdown() {
+  group = "distribution"
+  description = "Create the minimal and full getdown app folder for installers and website and create digest file"
+  dependsOn getdownDigest
+}
+
+clean {
+  delete project.ext.getdownWebsiteDir
+  delete project.ext.getdownFilesDir
+}
+
+install4j {
+  def install4jHomeDir = "/opt/install4j"
+  def hostname = "hostname".execute().text.trim()
+  if (hostname.equals("jv-bamboo")) {
+    install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
+  } else if (OperatingSystem.current().isMacOsX()) {
+    install4jHomeDir = '/Applications/install4j.app/Contents/Resources/app'
+    if (! file(install4jHomeDir).exists()) {
+      install4jHomeDir = System.getProperty("user.home")+install4jHomeDir
+    }
+  } else if (OperatingSystem.current().isLinux()) {
+    install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
+  }
+  installDir = file(install4jHomeDir)
+  mediaTypes = Arrays.asList(install4jMediaTypes.split(","))
+  if (install4jFaster.equals("true")) {
+    faster = true
+  }
+}
+
+def install4jConf
+def macosJavaVMDir
+def macosJavaVMTgz
+def windowsJavaVMDir
+def windowsJavaVMTgz
+def install4jDir = "$jalviewDir/$install4jResourceDir"
+def install4jConfFile = "jalview-installers-java"+JAVA_VERSION+".install4j"
+install4jConf = "$install4jDir/$install4jConfFile"
+
+task copyInstall4jTemplate(type: Copy) {
+  macosJavaVMDir = System.env.HOME+"/buildtools/jre/openjdk-java_vm/getdown/macos-jre"+JAVA_VERSION+"/jre"
+  macosJavaVMTgz = System.env.HOME+"/buildtools/jre/openjdk-java_vm/install4j/tgz/macos-jre"+JAVA_VERSION+".tar.gz"
+  windowsJavaVMDir = System.env.HOME+"/buildtools/jre/openjdk-java_vm/getdown/windows-jre"+JAVA_VERSION+"/jre"
+  windowsJavaVMTgz = System.env.HOME+"/buildtools/jre/openjdk-java_vm/install4j/tgz/windows-jre"+JAVA_VERSION+".tar.gz"
+  from (install4jDir) {
+    include install4jTemplate
+    rename (install4jTemplate, install4jConfFile)
+    filter(ReplaceTokens, beginToken: '', endToken: '', tokens: ['9999999999': JAVA_VERSION])
+    filter(ReplaceTokens, beginToken: '$$', endToken: '$$',
+      tokens: [
+        'JAVA_VERSION': JAVA_VERSION,
+        'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
+        'VERSION': JALVIEW_VERSION,
+        'MACOS_JAVA_VM_DIR': macosJavaVMDir,
+        'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
+        'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
+        'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
+        'INSTALL4JINFOPLISTFILEASSOCIATIONS': install4jInfoPlistFileAssociations,
+        'COPYRIGHT_MESSAGE': install4jCopyrightMessage,
+        'MACOS_BUNDLE_ID': install4jMacOSBundleId
+      ]
+    )
+    if (OSX_KEYPASS=="") {
+      filter(ReplaceTokens, beginToken: 'codeSigning macEnabled="', endToken: '"', tokens: ['true':'codeSigning macEnabled="false"'])
+      filter(ReplaceTokens, beginToken: 'runPostProcessor="true" ',endToken: 'Processor', tokens: ['post':'runPostProcessor="false" postProcessor'])
+    }
+  }
+  into install4jDir
+  outputs.files(install4jConf)
+
+  doLast {
+    def installerFileAssociationsXml = file("$install4jDir/$install4jInstallerFileAssociations").text
+    ant.replaceregexp(
+      byline: false,
+      flags: "s",
+      match: '<action name="EXTENSIONS_REPLACED_BY_GRADLE".*?</action>',
+      //match: '<action.*?EXTENSIONS_REPLACED_BY_GRADLE.*?</action>',
+      replace: installerFileAssociationsXml,
+      file: install4jConf
+    )
+  }
+}
+
+task installers(type: com.install4j.gradle.Install4jTask) {
+  group = "distribution"
+  description = "Create the install4j installers"
+  dependsOn getdown
+  dependsOn copyInstall4jTemplate
+  projectFile = file(install4jConf)
+  println("Using projectFile "+projectFile)
+  variables = [majorVersion: version.substring(2, 11), build: 001, OSX_KEYSTORE: OSX_KEYSTORE, JSIGN_SH: JSIGN_SH]
+  destination = "$jalviewDir/$install4jBuildDir/$JAVA_VERSION"
+  buildSelected = true
+
+  if (OSX_KEYPASS) {
+    macKeystorePassword=OSX_KEYPASS
+    
+  }
+  
+  inputs.dir(project.ext.getdownWebsiteDir)
+  inputs.file(install4jConf)
+  inputs.dir(macosJavaVMDir)
+  inputs.dir(windowsJavaVMDir)
+  outputs.dir("$jalviewDir/$install4jBuildDir/$JAVA_VERSION")
+}
+
+clean {
+  delete install4jConf
+}
diff --git a/build.xml b/build.xml
deleted file mode 100755 (executable)
index 9cd966c..0000000
--- a/build.xml
+++ /dev/null
@@ -1,1016 +0,0 @@
-<?xml version="1.0"?>
-<!--
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ 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.
--->
-<project name="jalviewX" default="usage" basedir="."
- xmlns:if="ant:if"
-    xmlns:unless="ant:unless">
-  <taskdef classpath="${clover.jar}" resource="cloverlib.xml" if:set="clover.jar"/>
-  <clover-env if:set="clover.jar"/>
-
-  <target name="help" depends="usage" />
-  <target name="usage" depends="init">
-    <echo message="~~~Jalview Ant build.xml Usage~~~~" />
-    <echo message="Targets include:" />
-    <echo message="usage - default target, displays this message" />
-    <echo message="buildindices - generates JavaHelpSearch from the help files" />
-    <echo message="build - compiles all necessary files for Application" />
-    <echo message="makedist - compiles and places all necessary jar files into directory dist" />
-    <echo message="makefulldist - signs all jar files and builds jnlp file for full distribution" />
-    <echo message="              this needs a keystore and key."/>
-    <echo message="              Add -Dtimestamp to timestamp signed jars"/>
-    <echo message="              -Djalview.keyalg and -Djalview.keydig are SHA1/SHA1withRSA"/>
-    <echo message="              See docs/building.html for more information." />
-    <echo message="compileApplet - compiles all necessary files for Applet" />
-    <echo message="makeApplet - compiles, then packages and obfuscates the Applet" />
-    <echo message="testng - run jalview's tests via testNG. Default group is '${testng-groups}'" />
-    <echo message="      you can specify particular test groups as a list via -Dtestng-groups=" />
-    <echo message="See docs/building.html and the comments in build file for other targets." />
-    <echo message="note: compile and makeApplet optionally compile/obfuscate applet against a different Java version by specifying -Djava118.home=PathtoJDK/lib which is the lib directory in the JDK install that contains rt.jar " />
-    <echo message="Useful -D flags: -Ddonotobfuscate will prevent applet obfuscation" />
-    <echo message="Useful -D flags: -Dclover.jar to specify path to openclover for testng coverage report" />
-  </target>
-
-
-  <!-- utils is a class path to additional utilities needed for
-    building docs, jars and webstart stuff -->
-  <!--
-        Userdefined build property defaults
-
-        wsdl.server list (plus namespace mapping info ???)  - also want
-                ... to make this a dynamically generatable property
-        WebStart Location
-        Build location - provide a temporary root for speed
-        jarsigner keystore and info
-        Jakarta and axis classpath ?
-        Default argument for starting Jalview (if it exists).
-
--->
-
-  <target name="init">
-    <path id="axis.classpath">
-      <!-->
-      <fileset dir="/usr/local/axis/lib">
-        <include name="**/*.jar" />
-      </fileset>
-      <fileset dir="/usr/local/jakarta-tomcat-5/webapps/axis/WEB-INF/lib">
-        <include name="**/*.jar"/>
-        <include name="*.jar"/>
-      </fileset> -->
-      </path>
-
-    <!-- Jalview Version String displayed by application on startup and used to check for updates -->
-    <property name="JALVIEW_VERSION" value="DEVELOPMENT" />
-
-    <property name="INSTALLATION" value="Source" />
-
-    <!-- 2.4 (VAMSAS)" -->
-    <!-- Include debugging information in javac true or false -->
-    <property name="javac.debug" value="true" />
-
-    <!-- JarSigner Key Store for Webstart Distribution -->
-    <property name="jalview.keystore" value="./keys/.keystore" />
-    <!-- Keystore Password -->
-    <property name="jalview.keystore.pass" value="alignmentisfun" />
-    <!-- Key Name -->
-    <property name="jalview.key" value="jalview" />
-    <!-- Key Password -->
-    <property name="jalview.key.pass" value="alignmentisfun" />
-    <!-- time stamp server URL -->
-    <property name="jalview.tsaurl" value="" />
-    <!-- locally valid proxy for signing with external time server -->
-    <property name="proxyPort" value="80"/>
-    <property name="proxyHost" value="sqid"/>
-    <!-- key sign/digest algorithms -->
-    <property name="jalview.keyalg" value="SHA1withRSA" description="key algorithm for signing"/>
-    <property name="jalview.keydig" value="SHA1" description="algorithm for jar digest"/>
-
-    <!-- default TestNG groups to run -->
-    <property name="testng-groups" value="Functional" />
-    <!-- Java 9 JVM args -->
-    <condition property="java9">
-      <equals arg1="${ant.java.version}" arg2="9"/>
-    </condition>
-
-    <!-- Don't change anything below here unless you know what you are doing! -->
-    <!-- Url path for WebStart in JNLP file -->
-    <property name="WebStartLocation" value="http://www.jalview.org/webstart" />
-    <!-- Webstart Image - looked for in resources/images -->
-    <property name="WebStartImage" value="JalviewLogo_big.png" />
-    <!-- J2SE version needed for webstart launch -->
-    <!-- Anne's version needs 1.7 - should rebuild VARNA to java 1.6 for release -->
-    <property name="j2sev" value="1.7+" />
-    <!-- Java Compilation settings - source and target javac version -->
-    <property name="javac.source" value="1.8" />
-    <property name="javac.target" value="1.8" />
-
-    <!-- Permissions for running Java applets and applications. -->
-    <!-- Defaults are those suitable for deploying jalview webstart www.jalview.org -->
-    <property name="application.codebase" value="*.jalview.org" />
-    <!-- and allowing the applet to be deployed from any URL -->
-    <!-- note - if you want to make sure LiveConnect works without any warnings, please rebuild and sign your applet jar with your own domain included in the codebase/allowable-codebase properties -->
-    <property name="applet.codebase" value="*.jalview.org *.dundee.ac.uk *" />
-    <property name="applet.caller-codebase" value="${applet.codebase}" />
-
-    <!-- build directory configuration -->
-    <property name="libDir" value="lib" />
-    <property name="resourceDir" value="resources" />
-    <property name="helpDir" value="help" />
-    <property name="docDir" value="doc" />
-    <property name="sourceDir" value="src" />
-    <property name="schemaDir" value="schemas" />
-    <property name="outputDir" value="classes" unless:set="clover.jar"/>
-    <property name="outputDir" value="cloverclasses" if:set="clover.jar"/>
-    <property name="packageDir" value="dist" />
-    <property name="outputJar" value="jalview.jar" />
-    <!-- Jalview Applet JMol Jar Dependency -->
-    <property name="jmolJar" value="JmolApplet-14.6.4_2016.10.26.jar" />
-    <property name="varnaJar" value="VARNAv3-93.jar" />
-    <property name="jsoup" value="jsoup-1.8.1.jar" />
-    <property name="jsonSimple" value="json_simple-1.1.jar" />
-    <property name="javaJson" value="java-json.jar" />
-    <property name="jalviewLiteJar" value="jalviewApplet.jar" />
-    <property name="reportDir" value="test-reports" />
-    <property name="testDir" value="test" />
-    <property name="testOutputDir" value="tests" />
-    <!-- switch to indicate if we should obfuscate jalviewLite -->
-    <!-- <property name="donotobfuscate" value="true"/> -->
-    <!-- switch to exclude associations from generated jnlp files -->
-    <!-- <property name="nojnlpfileassocs" value="true"/> -->
-
-    <!-- Jalview Web Service Clients - see the comments in 'buildextclients' for details -->
-    <property name="wsdl.File" value="http://www.compbio.dundee.ac.uk/JalviewWS/services/jpred?wsdl" />
-    <property name="wsdl.Files" value="http://www.compbio.dundee.ac.uk/JalviewWS/services/vamsas?wsdlFiles" />
-    <property name="wsdl.MsaWS" value="http://www.compbio.dundee.ac.uk/JalviewWS/services/MuscleWS?wsdl" />
-    <property name="wsdl.MsaWS2" value="http://www.compbio.dundee.ac.uk/JalviewWS/services/ClustalWS?wsdl" />
-    <property name="WSInterf" value="MsaWS" />
-    <property name="wsdl.Namespace" value="vamsas" />
-    <property name="wsdl.ClientNS" value="ext.vamsas" />
-    <!-- the class path for building the application -->
-    <path id="build.classpath">
-      <fileset dir="utils">
-        <include name="*.jar" />
-        <include name="**/*.jar" />
-      </fileset>
-      <fileset dir="${libDir}">
-        <include name="*.jar" />
-        <include name="**/*.jar" />
-      </fileset>
-      <fileset dir="${java.home}/lib">
-        <include name="plugin.jar" />
-      </fileset>
-      <fileset dir="appletlib">
-        <!-- the JmolApplet includes the JmolApplet console and the application javac seems to always try and build all packages 
-                               -->
-        <include name="${jmolJar}" />
-        <include name="${varnaJar}" />
-      </fileset>
-    </path>
-    <path id="test.classpath">
-      <pathelement path="${outputDir}" />
-      <path refid="build.classpath" />
-    </path>
-    <property name="source.dist.name" value="${basedir}/jalview-src.tar.gz" />
-    <!-- The Location of the java 1.1.8 jdk -->
-    <property name="java118.home" value="${java.home}" />
-    <!-- jre for 1.4 version -->
-    <property name="applet.jre.tools" value="${java118.home}/lib/rt.jar" />
-
-    <!-- the classpath for building the 1.1 applet -->
-    <path id="jalviewlite.deps">
-      <fileset dir="${java118.home}">
-        <include name="lib/rt.jar" />
-      </fileset>
-      <fileset dir="${java.home}/lib">
-        <include name="plugin.jar" />
-      </fileset>
-      <pathelement location="appletlib/${jmolJar}" />
-      <pathelement location="lib/${varnaJar}" />
-      <pathelement location="appletlib/${jsoup}" />
-      <pathelement location="appletlib/${jsonSimple}" />
-      <pathelement location="appletlib/${javaJson}" />
-
-    </path>
-    <!-- default location for outputting javadoc -->
-    <property name="javadocDir" value="${packageDir}/javadoc" />
-  </target>
-
-
-  <taskdef classpath="utils/roxes-ant-tasks-1.2-2004-01-30.jar" resource="com/roxes/tools/ant/taskdefs.properties" />
-  <target name="buildPropertiesFile" depends="init">
-    <tstamp prefix="build">
-      <format property="date" pattern="dd MMMM yyyy" />
-    </tstamp>
-    <exec executable="/usr/bin/git" outputproperty="git.commit" failifexecutionfails="false">
-      <arg value="rev-parse" />
-      <arg value="--short" />
-      <arg value="HEAD" />
-    </exec>
-    <exec executable="/usr/bin/git" outputproperty="git.branch" failifexecutionfails="false">
-      <arg value="rev-parse" />
-      <arg value="--abbrev-ref" />
-      <arg value="HEAD" />
-    </exec>
-    <properties file="${outputDir}/.build_properties">
-      <header>
-          ---Jalview Build Details---
-        </header>
-      <property name="VERSION" value="${JALVIEW_VERSION}" />
-      <property name="INSTALLATION" value="${INSTALLATION} git-commit:${git.commit} [${git.branch}]" />
-      <property name="BUILD_DATE" value="${build.date}" />
-    </properties>
-  </target>
-
-
-  <target name="clean" depends="init">
-    <!-- not efficient yet. -->
-    <delete dir="${outputDir}" includes="*,**/*" />
-  </target>
-
-  <target name="distclean" depends="init, clean">
-
-    <echo message="REMOVING ALL BACKUP/AUTOSAVES!" />
-    <delete>
-      <fileset dir=".">
-        <include name="${outputJar}" />
-        <include name="#*#" />
-        <include name="#*.*#" />
-        <include name="**/#*#" />
-        <include name="**/#*.*#" />
-        <include name="*~" />
-        <include name="*.*~" />
-        <include name="**/*~" />
-        <include name="**/*.*~" />
-      </fileset>
-    </delete>
-  </target>
-
-  <target name="prepare" depends="init">
-    <mkdir dir="${outputDir}" />
-    <copy todir="${outputDir}">
-      <fileset dir=".">
-        <include name="${docDir}/**/*.*" />
-        <include name="${helpDir}/**/*.*" />
-        <include name="${libDir}/*.jar" />
-      </fileset>
-      <fileset dir="${resourceDir}">
-        <include name="**/*.*" />
-      </fileset>
-    </copy>
-  </target>
-
-  <target name="build" depends="prepare">
-    <!-- not efficient yet. -->
-    <javac source="${javac.source}" target="${javac.target}" srcdir="${sourceDir}" destdir="${outputDir}" debug="${javac.debug}" classpathref="build.classpath">
-      <exclude name="jalview/*applet*" />
-      <exclude name="jalview/appletgui/**" />
-      <exclude name="com/stevesoft/**" />
-    </javac>
-  </target>
-
-
-  <target name="testclean" depends="init">
-    <delete dir="${testOutputDir}" includes="*,**/*" />
-  </target>
-
-  <target name="prepareTests" depends="init,testclean">
-    <mkdir dir="${testOutputDir}" />
-  </target>
-
-  <target name="buildTests" depends="build,buildindices,prepareTests">
-    <javac source="${javac.source}" target="${javac.target}" srcdir="${testDir}" destdir="${testOutputDir}" debug="${javac.debug}" classpathref="test.classpath" includeantruntime="false">
-    </javac>
-  </target>
-
-  <taskdef resource="testngtasks" classpath="utils/testnglibs/testng.jar" />
-
-  <target name="testng" depends="buildTests">
-    <testng outputDir="${reportDir}" haltOnFailure="false" groups="${testng-groups}" mode="testng"
-      verbose="2">
-      <classpath>
-        <pathelement location="${testOutputDir}" />
-        <pathelement location="${clover.jar}" if:set="clover.jar"/>
-        <path refid="test.classpath" />
-      </classpath>
-      <jvmarg value="--add-modules=java.se.ee" if:set="java9"/>
-      <jvmarg value="--illegal-access=warn" if:set="java9"/>
-      <classfileset dir="${testOutputDir}" includes="**/*.class" />
-    </testng>
-  </target>
-
-  <target name="buildindices" depends="init, prepare" unless="help.uptodate">
-    <replace value="${JALVIEW_VERSION}">
-      <replacetoken><![CDATA[$$Version-Rel$$]]></replacetoken>
-      <fileset dir="${outputDir}/${helpDir}">
-        <include name="help.jhm" />
-      </fileset>
-    </replace>
-
-    <java classname="com.sun.java.help.search.Indexer" classpathref="build.classpath" fork="true" dir="${outputDir}/${helpDir}">
-      <arg line="html" />
-    </java>
-  </target>
-
-  <target name="preparejnlp" depends="makedist">
-    <copy todir="${packageDir}">
-      <fileset dir="${resourceDir}/images">
-        <include name="${WebStartImage}" />
-      </fileset>
-    </copy>
-
-    <taskdef classpathref="build.classpath" resource="com/roxes/tools/ant/taskdefs.properties" />
-
-    <!-- create a dummy jar which will eventually contain the jnlp template -->
-    <jar destfile="${packageDir}/jalview_jnlp_vm.jar" index="true">
-      <fileset dir="${packageDir}">
-        <include name="jalview.jar" />
-      </fileset>
-    </jar>
-
-    <mkdir dir="${packageDir}/JNLP-INF" />
-    <antcall target="writejnlpf">
-      <param name="jnlpFile" value="${packageDir}/JNLP-INF/APPLICATION-TEMPLATE.JNLP" />
-      <param name="inih" value="*" />
-      <param name="maxh" value="*" />
-    </antcall>
-
-    <jar destfile="${packageDir}/jalview_jnlp_vm.jar" index="true">
-      <fileset dir="${packageDir}">
-        <include name="JNLP-INF/APPLICATION-TEMPLATE.JNLP" />
-      </fileset>
-    </jar>
-
-    <antcall target="writejnlpf">
-      <param name="jnlpFile" value="${packageDir}/jalview_256M.jnlp" />
-      <param name="inih" value="10M" />
-      <param name="maxh" value="256M" />
-    </antcall>
-    <antcall target="writejnlpf">
-      <param name="jnlpFile" value="${packageDir}/jalview.jnlp" />
-      <param name="inih" value="800M" />
-      <param name="maxh" value="1024M" />
-    </antcall>
-
-    <antcall target="writejnlpf">
-      <param name="jnlpFile" value="${packageDir}/jalview_1G.jnlp" />
-      <param name="inih" value="128M" />
-      <param name="maxh" value="512M" />
-    </antcall>
-
-    <antcall target="writejnlpf">
-      <param name="jnlpFile" value="${packageDir}/jalview_2G.jnlp" />
-      <param name="inih" value="800M" />
-      <param name="maxh" value="1024M" />
-    </antcall>
-
-    <!-- finally, need to postprocess to add in associations at end of 'information' element 
-                       
-                       <xslt in="${packageDir}/jalview_noa_1G.jnlp" out="${packageDir}/jalview_1G.jnlp">
-               
-               </xslt>
-                       
-                       
-                       -->
-    <!--
-                               <association mime-type="application-x/ext-file" extensions="fa"/>
-        <association mime-type="application-x/ext-file" extensions="fasta"/>
-        <association mime-type="application-x/ext-file" extensions="mfa"/>
-        <association mime-type="application-x/ext-file" extensions="fastq"/>
-        <association mime-type="application-x/ext-file" extensions="blc"/>
-        <association mime-type="application-x/ext-file" extensions="msf"/>
-        <association mime-type="application-x/ext-file" extensions="pfam"/>
-        <association mime-type="application-x/ext-file" extensions="aln"/>
-        <association mime-type="application-x/ext-file" extensions="pir"/>
-        <association mime-type="application-x/ext-file" extensions="amsa"/>
-        <association mime-type="application-x/ext-file" extensions="stk"/>
-        <association mime-type="application-x/ext-file" extensions="jar"/>-->
-  </target>
-
-  <target name="-jarsignwithtsa" depends="makedist,preparejnlp" if="timestamp" unless="nosign">
-    <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" sigalg="${jalview.keyalg}" digestalg="${jalview.keydig}"
-      tsaproxyhost="${proxyHost}" tsaproxyport="${proxyPort}" tsaurl="${jalview.tsaurl}">
-      <fileset dir="${packageDir}">
-        <include name="*.jar" />
-      </fileset>
-    </signjar>
-  </target>
-  <target name="-jarsignnotsa" depends="makedist,preparejnlp" if:blank="timestamp" unless="nosign">
-    <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" sigalg="${jalview.keyalg}" digestalg="${jalview.keydig}">
-      <fileset dir="${packageDir}">
-        <include name="*.jar" />
-      </fileset>
-    </signjar>
-  </target>
-
-  <target name="makefulldist" depends="makedist,preparejnlp,-jarsignwithtsa,-jarsignnotsa">
-    <!-- and sign the jars -->
-    <!-- the default keystore details might need to be edited here -->
-  </target>
-
-  <target name="runenv" depends="init">
-    <path id="run.classpath">
-      <pathelement location="${outputDir}" />
-      <fileset dir="${outputDir}">
-        <include name="${libDir}/*.jar" />
-      </fileset>
-    </path>
-    <pathconvert targetos="unix" refid="run.classpath" property="run.classpath" />
-
-    <echo>java -classpath ${run.classpath} jalview.bin.Jalview
-      </echo>
-  </target>
-
-  <target name="-generatejnlpf">
-    <presetdef name="jnlpf">
-      <jnlp codebase="${WebStartLocation}">
-        <information>
-          <title>Jalview</title>
-          <vendor>The Barton Group</vendor>
-          <homepage href="http://www.jalview.org" />
-          <description>Jalview Multiple Alignment Editor</description>
-          <description kind="short">Jalview</description>
-          <icon href="${WebStartImage}" />
-          <offline_allowed />
-        </information>
-        <resources>
-          <j2se version="1.8+" />
-          <jar main="true" href="jalview.jar"/>
-          <fileset dir="${packageDir}">
-            <exclude name="jalview.jar" />
-            <include name="*.jar" />
-            <include name="*_*.jar" />
-            <exclude name="*quaqua*.jar" />
-          </fileset>
-          <property name="jalview.version" value="${JALVIEW_VERSION}" />
-        </resources>
-        <resources os="Mac OS X">
-          <fileset dir="${packageDir}">
-                <include name="quaqua-filechooser-only-8.0.jar"/>
-            <include name="*quaqua64*.jnilib.jar" />
-          </fileset>
-        </resources>
-
-        <application_desc main_class="jalview.bin.Jalview">
-        </application_desc>
-        <security>
-          <all_permissions />
-        </security>
-      </jnlp>
-    </presetdef>
-
-    <jnlpf toFile="${jnlpFile}" />
-    <!-- add the add-modules j2se attribute for java 9 -->
-    <replace file="${jnlpFile}" value="j2se version=&quot;1.8+&quot; initial-heap-size=&quot;${inih}&quot; max-heap-size=&quot;${maxh}&quot; java-vm-args=&quot;--add-modules=java.se.ee --illegal-access=warn&quot;">
-          <replacetoken>j2se version="1.8+"</replacetoken>
-    </replace>
-  </target>
-
-  <target name="-dofakejnlpfileassoc" depends="-generatejnlpf" if="nojnlpfileassocs">
-    <echo message="Not adding JNLP File Associations" />
-  </target>
-
-  <target name="-dojnlpfileassoc" depends="-generatejnlpf" unless="nojnlpfileassocs">
-    <replace file="${jnlpFile}">
-      <replacetoken>
-        <![CDATA[</information>]]></replacetoken>
-      <replacevalue>
-        <![CDATA[
-          <association mime-type="application-x/ext-file" extensions="fa" />
-        <association mime-type="application-x/ext-file" extensions="fasta" />
-        <association mime-type="application-x/ext-file" extensions="mfa" />
-        <association mime-type="application-x/ext-file" extensions="fastq" />
-        <association mime-type="application-x/ext-file" extensions="blc" />
-        <association mime-type="application-x/ext-file" extensions="msf" />
-        <association mime-type="application-x/ext-file" extensions="pfam" />
-        <association mime-type="application-x/ext-file" extensions="aln"/>
-        <association mime-type="application-x/ext-file" extensions="pir"/>
-        <association mime-type="application-x/ext-file" extensions="amsa"/>
-        <association mime-type="application-x/ext-file" extensions="stk"/>
-        <association mime-type="application-x/ext-file" extensions="jvp"/>
-      </information>]]></replacevalue>
-  </replace>
-  <echo message="Added file associations to JNLP file" />
-</target>
-<target name="writejnlpf" depends="-dojnlpfileassoc,-dofakejnlpfileassoc">
-</target>
-
-<target name="buildextclients" depends="init">
-  <input message="Building external client source from WSDLs - Do you really want to do this ? (Yy/Nn)" validargs="Y,y,n,N" defaultvalue="N" addproperty="doextbuild.response" />
-  <condition property="dontextbuild">
-    <equals arg1="n" arg2="${doextbuild.response}" />
-  </condition>
-  <condition property="dontextbuild">
-    <equals arg1="N" arg2="${doextbuild.response}" />
-  </condition>
-  <fail if="dontextbuild">
-        Build External Client Code process aborted by user. Jalview source is unchanged.
-      </fail>
-  <!-- Currently, this doesn't happen automatically.
-     1. Run WSDL2Java as below, which generates an ext.vamsas +
-     vamsas.<datapackages> fileset.
-     2. refactor ext.vamsas.SpecificserviceWS* to
-     ext.vamsas.ServiceclassWS* that implements the ServiceclassWSI interface.
-     3. Update the jalview.ws.*WServices classes to reflect the
-     interface type, and all locations of this wsdl type that Jalview
-     might be using.
-
--->
-  <path id="axisbuild">
-    <path refid="build.classpath" />
-  </path>
-  <taskdef resource="axis-tasks.properties" classpathref="axisbuild" />
-  <move todir="./bak">
-    <fileset dir="${sourceDir}" id="client">
-      <include name="${wsdl.ClientNS}/*.*" />
-    </fileset>
-  </move>
-
-  <axis-wsdl2java output="${sourceDir}" verbose="true" url="${wsdl.MsaWS2}" serverside="false" deployscope="Request" debug="false" helpergen="true" all="true">
-    <mappingSet>
-      <mapping namespace="${wsdl.Namespace}" package="${wsdl.ClientNS}" />
-      <mapping namespace="http://dataTypes.vamsas" package="${wsdl.ClientNS}" />
-    </mappingSet>
-  </axis-wsdl2java>
-</target>
-
-<target name="makedist" depends="build, buildPropertiesFile, linkcheck, buildindices">
-  <fail if="clover.jar">
-    Ignoring request to build jalview distribution with clover-instrumented classes
-  </fail>
-  <!-- make the package jar if not already existing -->
-  <mkdir dir="${packageDir}" />
-  <!-- clean dir if it already existed -->
-  <delete>
-    <fileset dir="${packageDir}">
-      <include name="*.jar" />
-    </fileset>
-  </delete>
-  <jar destfile="${packageDir}/${outputJar}" index="true">
-    <manifest>
-      <attribute name="Main-Class" value="jalview.bin.Jalview" />
-      <attribute name="Permissions" value="all-permissions" />
-      <attribute name="Application-Name" value="Jalview Desktop" />
-      <attribute name="Codebase" value="${application.codebase}" />
-    </manifest>
-    <fileset dir="${outputDir}/">
-      <exclude name="cache*/**" />
-      <exclude name="*.jar" />
-      <exclude name="*.jar.*" />
-      <exclude name="**/*.jar" />
-      <exclude name="**/*.jar.*" />
-    </fileset>
-  </jar>
-
-  <copy toDir="${packageDir}" flatten="true">
-    <fileset dir="${outputDir}">
-      <include name="*.jar" />
-      <include name="**/*.jar" />
-    </fileset>
-  </copy>
-</target>
-
-
-<!-- jalopy code reformatter -->
-<target name="sourcescrub" depends="init,build">
-  <jalopy destdir="jsrc" classpathref="run.classpath" convention="jalview-jalopy.xml">
-    <fileset dir="${sourceDir}">
-      <include name="*.java" />
-      <include name="**/*.java" />
-      <include name="**/**/*.java" />
-    </fileset>
-  </jalopy>
-</target>
-
-
-
-<!-- Compile, package and obfuscate Jalview Applet -->
-<target name="makeApplet" depends="obfuscate" description="assemble the final jalviewLite applet jar with or without obfuscation" />
-
-<target name="compileApplet" depends="init,clean">
-  <mkdir dir="${outputDir}" />
-  <javac source="${javac.source}" target="${javac.target}" srcdir="${sourceDir}" destdir="${outputDir}" debug="${javac.debug}" classpathref="jalviewlite.deps" includes="jalview/appletgui/**" excludes="ext/**,gui/**,jbgui/**,MCview/**,org/**,vamsas/**,jalview/ext/rbvi/**,jalview/ext/paradise/**,jalview/ext/ensembl/**,jalview/ext/so/**" />
-</target>
-
-<target name="packageApplet" depends="compileApplet, buildPropertiesFile">
-  <copy file="${resourceDir}/images/link.gif" toFile="${outputDir}/images/link.gif" />
-  <copy todir="${outputDir}/lang">
-    <fileset dir="${resourceDir}/lang">
-      <include name="**.*" />
-    </fileset>
-  </copy>
-  <jar destfile="in.jar" index="true">
-    <manifest>
-      <attribute name="Main-Class" value="jalview.bin.JalviewLite" />
-      <attribute name="Application-Name" value="JalviewLite" />
-      <attribute name="Codebase" value="${applet.codebase}" />
-    </manifest>
-    <fileset dir="${outputDir}">
-      <include name="com/**" />
-      <include name="MCview/**" />
-      <include name="jalview/**" />
-      <include name=".build_properties" />
-      <include name="images/link.gif" />
-      <include name="lang/**" />
-    </fileset>
-  </jar>
-</target>
-<target name="obfuscate" depends="-obfuscatefake,-obfuscatereally">
-</target>
-<target name="-obfuscatefake" depends="packageApplet" if="donotobfuscate">
-  <copy file="in.jar" tofile="${jalviewLiteJar}" overwrite="true" />
-  <delete file="in.jar" />
-</target>
-<target name="-obfuscatereally" unless="donotobfuscate">
-
-  <path id="obfuscateDeps.path">
-    <pathelement location="${applet.jre.tools}" />
-    <pathelement location="appletlib/${jmolJar}" />
-    <pathelement location="lib/${varnaJar}" />
-    <pathelement location="appletlib/${jsoup}" />
-    <pathelement location="appletlib/${jsonSimple}" />
-    <pathelement location="appletlib/${javaJson}" />
-    <fileset dir="${java.home}/lib">
-      <include name="plugin.jar" />
-    </fileset>
-  </path>
-  <taskdef resource="proguard/ant/task.properties" classpath="utils/proguard_5.3.3.jar" />
-
-  <proguard verbose="true" >
-    <injar file="in.jar" />
-    <outjar file="${jalviewLiteJar}" />
-    <libraryjar refid="obfuscateDeps.path" />
-    <dontwarn />
-    <keep access="public" type="class" name="jalview.bin.JalviewLite">
-      <field access="public" />
-      <method access="public" />
-      <constructor access="public" />
-    </keep>
-    <keep access="public" type="class" name="jalview.appletgui.AlignFrame">
-      <field access="public" />
-      <method access="public" />
-      <constructor access="public" />
-    </keep>
-
-    <keep name="jalview.json.binding**">
-      <constructor/>
-      <method name="*"/>
-    </keep>
-
-    <keep access="public" type="class" name="MCview.PDBfile">
-      <field access="public" />
-      <method access="public" />
-      <constructor access="public" />
-    </keep>
-
-    <keep access="public" type="class" name="jalview.ws.jws1.Annotate3D">
-      <field access="public" />
-      <method access="public" />
-      <constructor access="public" />
-    </keep>
-
-    <keep access="public" type="class" name="jalview.ext.jmol.JmolParser">
-      <field access="public" />
-      <method access="public" />
-      <constructor access="public" />
-    </keep>
-
-
-    <!--      -libraryjars "${obfuscateDeps}"
-      -injars      in.jar
-      -outjars     jalviewApplet.jar
-      -keep public class jalview.bin.JalviewLite
-       { public * ; } -->
-  </proguard>
-  <delete file="in.jar" />
-</target>
-
-<target name="jaxb-bindings" depends="init" description="Generates JAXB bindings for supported Jalview XML models (needs xjc on the path)">
-  <delete>
-    <fileset dir="${sourceDir}/jalview/xml/binding/jalview">
-      <include name="*.java" />
-    </fileset>
-  </delete>
-  <exec executable="xjc">
-    <arg value="${schemaDir}/jalview.xsd"/>
-    <arg value="-d"/>
-    <arg value="${sourceDir}"/>
-    <arg value="-p"/>
-    <arg value="jalview.xml.binding.jalview"/>
-  </exec>
-  <delete>
-    <fileset dir="${sourceDir}/jalview/xml/binding/embl">
-      <include name="*.java" />
-    </fileset>
-  </delete>
-
-  <exec executable="xjc">
-    <arg value="${schemaDir}/embl.xsd"/>
-    <arg value="-d"/>
-    <arg value="${sourceDir}"/>
-    <arg value="-b"/>
-    <arg value="${schemaDir}/embl_bindings.xml"/>
-    <arg value="-p"/>
-    <arg value="jalview.xml.binding.embl"/>
-  </exec>
-
-  <delete>
-    <fileset dir="${sourceDir}/jalview/xml/binding/uniprot">
-      <include name="*.java" />
-    </fileset>
-  </delete>
-
-  <exec executable="xjc">
-    <arg value="${schemaDir}/uniprot.xsd"/>
-    <arg value="-d"/>
-    <arg value="${sourceDir}"/>
-    <arg value="-p"/>
-    <arg value="jalview.xml.binding.uniprot"/>
-  </exec>
-</target>
-
-<target name="sourcedist" description="create jalview source distribution" depends="init">
-  <delete file="${source.dist.name}" />
-  <!-- temporary copy of source to update timestamps -->
-  <copy todir="_sourcedist">
-    <fileset dir=".">
-      <exclude name=".*" />
-      <exclude name="**/.*" />
-      <exclude name="*.class" />
-      <exclude name="**/*.class" />
-      <include name="LICENSE" />
-      <include name="README" />
-      <include name="build.xml" />
-      <include name="jalview-jalopy.xml" />
-      <include name="JalviewApplet.jpx" />
-      <include name="JalviewX.jpx" />
-      <include name="nbbuild.xml" />
-      <include name="nbproject/genfiles.properties" />
-      <include name="nbproject/project.properties" />
-      <include name="nbproject/project.xml" />
-      <include name="${sourceDir}/*.java" />
-      <include name="${sourceDir}/**/*.java" />
-      <include name="${sourceDir}/**/*.cdr" />
-      <include name="${libDir}/**/*" />
-      <include name="${resourceDir}/**/*" />
-      <include name="${helpDir}/**/*" />
-      <include name="appletlib/${jmolJar}" />
-      <include name="appletlib/${jsonSimple}" />
-      <include name="appletlib/${javaJson}" />
-      <exclude name="**/*locales" />
-      <exclude name="*locales/**" />
-      <exclude name="utils/InstallAnywhere/**Build.iap_xml" />
-      <exclude name="utils/InstallAnywhere/**Build*/**" />
-      <exclude name="utils/InstallAnywhere/**Build*/**" />
-      <exclude name="utils/InstallAnywhere/.build*.*/**" />
-      <exclude name="utils/InstallAnywhere/**locale*" />
-      <exclude name="utils/InstallAnywhere/**locale*/**" />
-      <exclude name="utils/InstallAnywhere/**locale*/**" />
-      <include name="${schemaDir}/**/*" />
-      <include name="utils/**/*" />
-      <include name="${docDir}/**/*" />
-      <include name="examples/**/*" />
-    </fileset>
-  </copy>
-
-  <tstamp prefix="build">
-    <format property="year" pattern="yyyy" />
-  </tstamp>
-  <!-- each replacetoken CDATA body must be on one line - 
-       otherwise the pattern doesn't match -->
-  <replace value="${JALVIEW_VERSION}">
-    <replacetoken><![CDATA[$$Version-Rel$$]]></replacetoken>
-    <fileset dir="_sourcedist">
-      <include name="**/*" />
-    </fileset>
-  </replace>
-  <replace dir="_sourcedist" value="${build.year}">
-    <replacetoken><![CDATA[$$Year-Rel$$]]></replacetoken>
-    <fileset dir="_sourcedist">
-      <include name="**/*" />
-    </fileset>
-  </replace>
-
-  <tar destfile="${source.dist.name}" compression="gzip">
-    <tarfileset dir="_sourcedist/" prefix="jalview" preserveLeadingSlashes="true">
-    </tarfileset>
-  </tar>
-
-  <delete dir="_sourcedist" />
-</target>
-<target name="prepubapplet_1" depends="makeApplet">
-  <copy todir="${packageDir}/examples">
-    <fileset dir="examples">
-      <include name="**/*" />
-      <include name="javascript/*" />
-      <include name="jmol/*" />
-    </fileset>
-    <fileset dir=".">
-      <include name="${jalviewLiteJar}" />
-    </fileset>
-    <fileset dir="appletlib">
-      <include name="**/*" />
-    </fileset>
-  </copy>
-  <jar update="true" index="true" jarfile="${packageDir}/examples/${jalviewLiteJar}" />
-  <jar update="true" index="true" jarfile="${packageDir}/examples/${javaJson}" />
-  <jar update="true" index="true" jarfile="${packageDir}/examples/${jsonSimple}" />
-  <jar update="true" index="true" jarfile="${packageDir}/examples/${jmolJar}">
-    <manifest>
-      <attribute name="Application-Name" value="Jmol (bundled with JalviewLite)" />
-      <!--          <attribute name="Permissions" value="sandbox" /> -->
-      <!--<attribute name="Trusted-Lib" value="true" /> -->
-      <attribute name="Codebase" value="${applet.codebase}" />
-      <attribute name="Caller-Allowable-Codebase" value="${applet.caller-codebase}" />
-    </manifest>
-  </jar>
-
-  <presetdef name="ap_applet.jar">
-    <!-- build a signed applet with 'all-permissions' - 
-                         Needs 'param name="permissions' value="all-permissions"' in applet tag
-                         JalviewLite+JmolApplet linked sequence/structure fails
-                         Mixed code warnings are raised
-                         -->
-    <jar update="true" index="true">
-      <manifest>
-        <attribute name="Application-Name" value="JalviewLite" />
-        <attribute name="Permissions" value="all-permissions" />
-        <attribute name="Codebase" value="${applet.codebase}" />
-        <attribute name="Caller-Allowable-Codebase" value="${applet.caller-codebase}" />
-        <attribute name="Application-Library-Allowable-Codebase" value="${applet.codebase}" />
-      </manifest>
-    </jar>
-  </presetdef>
-  <presetdef name="applet.jar">
-    <!-- build signed applet with sandbox permissions -
-                         Needs 'param name="permissions' value="sandbox"' in applet tag
-                        Preserves Pre-Java 1.7_u45 behavior once 'permissions' parameter added to applet tag 
-                       -->
-
-    <jar update="true" index="true">
-      <manifest>
-        <attribute name="Application-Name" value="JalviewLite" />
-        <attribute name="Permissions" value="sandbox" />
-        <attribute name="Codebase" value="${applet.codebase}" />
-        <attribute name="Caller-Allowable-Codebase" value="${applet.caller-codebase}" />
-        <attribute name="Application-Library-Allowable-Codebase" value="${applet.codebase}" />
-      </manifest>
-    </jar>
-  </presetdef>
-  <presetdef name="tl_applet.jar">
-    <!-- build signed applet with trusted library/trusted permissions -
-                               Needs 'param name="permissions' value="all-permissions"' in applet tag
-                              j1.7_45:
-                              No mixed code warnings raised 
-                              Jmol/JalviewLite sequence/structure example doesn't link structures
-                              Raises dialog asking user to allow page to control applet via LiveConnect javascript
-                              
-                             -->
-
-    <jar update="true" index="true">
-      <manifest>
-        <attribute name="Application-Name" value="JalviewLite" />
-        <attribute name="Permissions" value="all-permissions" />
-        <attribute name="Codebase" value="${applet.codebase}" />
-        <attribute name="Trusted-Only" value="true" />
-        <attribute name="Trusted-Library" value="true" />
-      </manifest>
-    </jar>
-  </presetdef>
-  <presetdef name="to_applet.jar">
-    <!-- not fully test variant (yet) -->
-    <jar update="true" index="true">
-      <manifest>
-        <attribute name="Application-Name" value="JalviewLite" />
-        <attribute name="Permissions" value="all-permissions" />
-        <attribute name="Codebase" value="${applet.codebase}" />
-        <attribute name="Trusted-Only" value="true" />
-      </manifest>
-    </jar>
-  </presetdef>
-  <!-- create differently privileged artefacts -->
-  <copy file="${packageDir}/examples/${jalviewLiteJar}" tofile="${packageDir}/examples/u_${jalviewLiteJar}" />
-  <copy file="${packageDir}/examples/${jmolJar}" tofile="${packageDir}/examples/u_${jmolJar}" overwrite="true" />
-  <copy file="${packageDir}/examples/${javaJson}" tofile="${packageDir}/examples/u_${javaJson}" overwrite="true" />
-  <copy file="${packageDir}/examples/${jsonSimple}" tofile="${packageDir}/examples/u_${jsonSimple}" overwrite="true" />
-  <copy file="${packageDir}/examples/${jalviewLiteJar}" tofile="${packageDir}/examples/ap_${jalviewLiteJar}" />
-  <copy file="${packageDir}/examples/${jmolJar}" tofile="${packageDir}/examples/ap_${jmolJar}" />
-  <copy file="${packageDir}/examples/${javaJson}" tofile="${packageDir}/examples/ap_${javaJson}" />
-  <copy file="${packageDir}/examples/${jsonSimple}" tofile="${packageDir}/examples/ap_${jsonSimple}" />
-  <ap_applet.jar jarfile="${packageDir}/examples/ap_${jalviewLiteJar}" />
-  <ap_applet.jar jarfile="${packageDir}/examples/ap_${jmolJar}" />
-  <ap_applet.jar jarfile="${packageDir}/examples/ap_${javaJson}" />
-  <ap_applet.jar jarfile="${packageDir}/examples/ap_${jsonSimple}" />
-  <copy file="${packageDir}/examples/${jalviewLiteJar}" tofile="${packageDir}/examples/tl_${jalviewLiteJar}" />
-  <copy file="${packageDir}/examples/${jmolJar}" tofile="${packageDir}/examples/tl_${jmolJar}" />
-  <copy file="${packageDir}/examples/${javaJson}" tofile="${packageDir}/examples/tl_${javaJson}" />
-  <copy file="${packageDir}/examples/${jsonSimple}" tofile="${packageDir}/examples/tl_${jsonSimple}" />
-  <tl_applet.jar jarfile="${packageDir}/examples/tl_${jalviewLiteJar}" />
-  <tl_applet.jar jarfile="${packageDir}/examples/tl_${jmolJar}" />
-  <tl_applet.jar jarfile="${packageDir}/examples/tl_${javaJson}" />
-  <tl_applet.jar jarfile="${packageDir}/examples/tl_${jsonSimple}" />
-  <copy file="${packageDir}/examples/${jalviewLiteJar}" tofile="${packageDir}/examples/to_${jalviewLiteJar}" />
-  <copy file="${packageDir}/examples/${jmolJar}" tofile="${packageDir}/examples/to_${jmolJar}" />
-  <copy file="${packageDir}/examples/${javaJson}" tofile="${packageDir}/examples/to_${javaJson}" />
-  <copy file="${packageDir}/examples/${jsonSimple}" tofile="${packageDir}/examples/to_${jsonSimple}" />
-  <to_applet.jar jarfile="${packageDir}/examples/to_${jalviewLiteJar}" />
-  <to_applet.jar jarfile="${packageDir}/examples/to_${jmolJar}" />
-  <to_applet.jar jarfile="${packageDir}/examples/to_${javaJson}" />
-  <to_applet.jar jarfile="${packageDir}/examples/to_${jsonSimple}" />
-  <!-- finally, create manifest for original jars -->
-  <applet.jar jarfile="${packageDir}/examples/${jalviewLiteJar}" />
-  <applet.jar jarfile="${packageDir}/examples/${jmolJar}" />
-  <applet.jar jarfile="${packageDir}/examples/${javaJson}" />
-  <applet.jar jarfile="${packageDir}/examples/${jsonSimple}" />
-
-  <!-- todo - write examples/downloads for alternate versions of the applet 
-    probably don't need to do this now since we don't have alternate versions anymore !-->
-</target>
-<target name="-signapplet" depends="prepubapplet_1">
-  <fileset id="signappletjarset" dir="${packageDir}/examples">
-    <exclude name="u_*.jar" />
-    <include name="${jalviewLiteJar}" />
-    <include name="${jmolJar}" />
-    <include name="${javaJson}" />
-    <include name="${jsonSimple}" />
-    <include name="to_${jalviewLiteJar}" />
-    <include name="to_${jmolJar}" />
-    <include name="to_${javaJson}" />
-    <include name="to_${jsonSimple}" />
-    <include name="tl_${jalviewLiteJar}" />
-    <include name="tl_${jmolJar}" />
-    <include name="tl_${javaJson}" />
-    <include name="tl_${jsonSimple}" />
-    <include name="ap_${jalviewLiteJar}" />
-    <include name="ap_${jmolJar}" />
-    <include name="ap_${javaJson}" />
-    <include name="ap_${jsonSimple}" />
-  </fileset>
-</target>
-<target name="-signappletnotsa" if:blank="timestamp" depends="-signapplet" unless="nosign">
-  <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false">
-    <fileset refid="signappletjarset" />
-  </signjar>
-</target>
-
-<target name="-signapplettsa" if="timestamp" depends="-signapplet" unless="nosign">
-  <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" tsaproxyhost="${proxyHost}" tsaproxyport="${proxyPort}" tsaurl="${jalview.tsaurl}">
-    <fileset refid="signappletjarset" />
-  </signjar>
-</target>
-
-<target name="signApplet" description="internal target to sign applet" depends="-signappletnotsa,-signapplettsa" />
-
-<target name="pubapplet" description="installs the jalviewLite applet and dependent jars into an applet examples directory built under ${outputDir}" depends="makeApplet, signApplet">
-
-  <!-- bizarre bug causes JmolApplet to always get signed, even if excluded from above. so copy explicitly -->
-  <copy file="appletlib/${jmolJar}" tofile="${packageDir}/examples/u_${jmolJar}" overwrite="true" />
-  <copy file="appletlib/${jsonSimple}" tofile="${packageDir}/examples/u_${jsonSimple}" overwrite="true" />
-  <copy file="appletlib/${javaJson}" tofile="${packageDir}/examples/u_${javaJson}" overwrite="true" />
-  <!-- finally, replace any launchApp servlet tags with a version specification -->
-  <replace value="http://www.jalview.org/services/launchApp?version=${JALVIEW_VERSION}&quot;">
-    <replacetoken>
-      <![CDATA[http://www.jalview.org/services/launchApp"]]>
-    </replacetoken>
-    <fileset dir="${packageDir}/examples">
-      <include name="**/*.html" />
-    </fileset>
-  </replace>
-  <replace value="http://www.jalview.org/services/launchApp?version=${JALVIEW_VERSION}'">
-    <replacetoken>
-      <![CDATA[http://www.jalview.org/services/launchApp']]>
-    </replacetoken>
-    <fileset dir="${packageDir}/examples">
-      <include name="**/*.html" />
-    </fileset>
-  </replace>
-
-</target>
-<target name="sourcedoc" description="Create jalview source documentation pages" depends="init">
-  <javadoc destdir="${javadocDir}">
-    <packageset dir="${sourceDir}" includes="jalview/*,MCView/*">
-    </packageset>
-  </javadoc>
-</target>
-<target name="linkcheck" depends="init,prepare">
-  <javac srcdir="utils" destdir="utils" includes="HelpLinksChecker.java"/>
-  <java fork="true" dir="${helpDir}" classpath="utils" classname="HelpLinksChecker" failonerror="true">
-    <arg file="${helpDir}"/>
-    <arg value="-nointernet"/>
-  </java>
-</target>
-</project>
similarity index 81%
rename from doc/building.html
rename to doc/building-OLD.html
index ecde4d8..f5e0b28 100755 (executable)
 </head>
 <body>
 <h1>Building Jalview from Source</h1>
-<P>
+<p/>
+
+<p>
+You will need the following:<br/>
+<ul>
+  <li> Obtain: git</li>
+  <li> Build/run: Java Development Kit (JDK). JDK11 is now the recommended platform for developing with Jalview.  You can obtain a pre-compiled JDK11 for many platforms from https://adoptopenjdk.net/</li>
+  <li> Build: Gradle 5.1 or later.  On linux you can obtain this with your package manager (e.g. <code>sudo yum install gradle</code> or <code>sudo apt install gradle</code> or on macOS with brew with <code>brew install gradle</code></li>
+</ul>
+With any luck, once you check out the Jalview source from git and set your JAVA_HOME and PATH correctly, you just need to change to the <code>jalview</code> directory and run <code>gradle getdown</code>.
+</p>
+
+<h2>Obtaining Jalview</h2>
+<p>This is easiest achieved with git:
+<pre>git clone http://www.jalview.org/git/jalview.git</pre>
+Then you can <code>cd jalview</code> to get into the top level jalview dir.
+
+
+<p>
+<h2>Minimal Jalview build</h2>
+<p>To run Jalview, you just need the jalview classes and the .jar libraries that Jalview depends on.</p>
+
+
+<!--
 <p>
 You will need the following (hopefully):<br>
 <ul>
@@ -92,6 +115,7 @@ dist. But first you need to make your own key:
                mechanism (e.g. Netbeans' executable Jar and dependent lib directory).
                The hand-crafted ant build.xml is (currently) the only officially
                supported method of building distributable versions of Jalview.</p>
+        -->
 <address>
 <a href="mailto:help@jalview.org">Jalview development team</a>
 </address>
diff --git a/examples/exampleFile.jvp b/examples/exampleFile.jvp
new file mode 100644 (file)
index 0000000..458cc0f
Binary files /dev/null and b/examples/exampleFile.jvp differ
index 35c54d5..5fc2ad1 100644 (file)
@@ -43,7 +43,7 @@ conserved = { ->
     /*
      * to make a new instance for each alignment view
      */
-    getInstance: { AlignViewportI view, AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> conserved() },
+    getInstance: { AlignViewportI view, AnnotatedCollectionI coll -> conserved() },
     
     /*
      * method only needed if colour scheme has to recalculate
index 3f1f953..eb40cc6 100644 (file)
@@ -26,7 +26,7 @@ candy = { ->
     /*
      * to make a new instance for each alignment view
      */
-    getInstance: { AlignViewportI view, AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> candy() },
+    getInstance: { view, coll -> candy() },
     
     /*
      * method only needed if colour scheme has to recalculate
@@ -86,7 +86,7 @@ byWeight = { ->
     // this colour scheme is peptide-specific:
     isApplicableTo: { coll -> !coll.isNucleotide() },
     alignmentChanged: { coll, map -> },
-    getInstance: { coll, map -> byWeight() },
+    getInstance: { view, coll -> byWeight() },
     isSimple: { true },
     findColour: {res, col, seq, consensus, pid -> 
         switch (res) {
index e186766..2365c1e 100644 (file)
@@ -42,7 +42,7 @@ unconserved = { ->
     /*
      * to make a new instance for each alignment view
      */
-    getInstance: { AnnotatedCollectionI coll, Map<SequenceI, SequenceCollectionI> map -> unconserved() },
+    getInstance: { view, coll -> unconserved() },
     
     /*
      * method only needed if colour scheme has to recalculate
diff --git a/getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar b/getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar
new file mode 100644 (file)
index 0000000..7230883
Binary files /dev/null and b/getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar differ
diff --git a/getdown/lib/getdown-launcher.jar b/getdown/lib/getdown-launcher.jar
new file mode 100644 (file)
index 0000000..cb5f670
Binary files /dev/null and b/getdown/lib/getdown-launcher.jar differ
diff --git a/getdown/src/getdown/.project-MOVED b/getdown/src/getdown/.project-MOVED
new file mode 100644 (file)
index 0000000..ccd1d40
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>getdown</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..99f26c0
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs
new file mode 100644 (file)
index 0000000..f897a7f
--- /dev/null
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/getdown/src/getdown/.travis.yml b/getdown/src/getdown/.travis.yml
new file mode 100644 (file)
index 0000000..32f2196
--- /dev/null
@@ -0,0 +1,11 @@
+language: java
+sudo: false
+script: "mvn -B clean verify"
+
+cache:
+  directories:
+    - '$HOME/.m2/repository'
+
+jdk:
+  - openjdk7
+  - oraclejdk8
diff --git a/getdown/src/getdown/AUTHORS b/getdown/src/getdown/AUTHORS
new file mode 100644 (file)
index 0000000..5f389d0
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# This is the official list of the AUTHORS of Getdown for copyright purposes.
+#
+# This is not the full list of contributors, see
+# https://github.com/threerings/getdown/graphs/contributors
+# for the full list of contributors.
+#
+# Contributors assign copyright of their work to the authors listed in the this file to keep life
+# simple.
+
+Michael Bayne <mdb@samskivert.com>
+Ray Greenwell <ray@threerings.net>
diff --git a/getdown/src/getdown/CHANGELOG.md b/getdown/src/getdown/CHANGELOG.md
new file mode 100644 (file)
index 0000000..098651e
--- /dev/null
@@ -0,0 +1,180 @@
+# Getdown Releases
+
+## 1.8.3 - Unreleased
+
+* Added support for `nresource` resources which must be jar files that contain native libraries.
+  Prior to launching the application, these resources will be unpacked and their contents added to
+  the `java.library.path` system property.
+
+* When the app is updated to require a new version of the JVM, that JVM will be downloaded and used
+  immediately during that app invocation (instead of one invocation later). Via PR#169.
+
+* When a custom JVM is installed, old JVM files will be deleted prior to unpacking the new JVM. Via
+  PR#170.
+
+* Number of concurrent downloads now defaults to num-cores minus one. Though downloads are I/O
+  bound rather than CPU bound, this still turns out to be a decent default.
+
+* Avoid checking for proxy config if `https.proxyHost` is set. This matches existing behavior when
+  `http.proxyHost` is set.
+
+* Added support for proxy authentication. A deployment must also use the
+  `com.threerings.getdown.spi.ProxyAuth` service provider interface to persist the proxy
+  credentials supplied by the user. Otherwise they will be requested every time Getdown runs, which
+  is not a viable user experience.
+
+## 1.8.2 - Nov 27, 2018
+
+* Fixed a data corruption bug introduced at last minute into 1.8.1 release. Oops.
+
+## 1.8.1 - Nov 26, 2018
+
+* If both an `appbase` and `appdir` are provided via some means (bootstrap properties file, system
+  property, etc.) and the app dir does not yet exist, Getdown will create it.
+
+* Added `max_concurrent_downloads` setting to `getdown.txt`. Controls what you would expect.
+  Defaults to two.
+
+* `bootstrap.properties` can now contain system properties which will be set prior to running
+  Getdown. They must be prefixed by `sys.`: for example `sys.silent = true` will set the `silent`
+  system property to `true`.
+
+* If Getdown is run in a headless JVM, it will avoid showing a UI but will attempt to install and
+  launch the application anyhow. Note that passing `-Dsilent` will override this behavior (because
+  in silent mode the default is only to install the app, not also launch it).
+
+* Fixed issue with `appid` not being properly used when specified via command line arg.
+
+* Fixed issue with running Getdown on single CPU systems (or virtual systems). It was attempting to
+  create a thread pool of size zero, which failed.
+
+* Fixed issue with backslashes (or other regular expression escape characters) in environment
+  variables being substituted into app arguments.
+
+## 1.8.0 - Oct 19, 2018
+
+* Added support for manually specifying the thread pool size via `-Dthread_pool_size`. Also reduced
+  the default thread pool size to `num_cpus-1` from `num_cpus`.
+
+* Added support for bundling a `bootstrap.properties` file with the Getdown jar file, which can
+  specify defaults for `appdir`, `appbase` and `appid`.
+
+* Added support for a host URL whitelist. Getdown can be custom built to refuse to operate with any
+  URL that does not match the built-time-specified whitelist. See `core/pom.xml` for details.
+
+* Removed the obsolete support for running Getdown in a signed applet. Applets are no longer
+  supported by any widely used browser.
+
+* Split the project into multiple Maven modules. See the notes on [migrating from 1.7 to 1.8] for
+  details.
+
+* A wide variety of small cleanups resulting from a security review generously performed by a
+  prospective user. This includes various uses of deterministic locales and encodings instead of
+  the platform default locale/encoding, in cases where platform/locale-specific behavior is not
+  desired or needed.
+
+* Made use of `appid` fall back to main app class if no `appid`-specific class is specified.
+
+* Added support for marking resources as executable (via `xresource`).
+
+* Fixed issue where entire tracking URL was being URL encoded.
+
+* Changed translations to avoid the use of the term 'game'. Use 'app' instead.
+
+## 1.7.1 - Jun 6, 2018
+
+* Made it possible to use `appbase_domain` with `https` URLs.
+
+* Fixed issue with undecorated splash window being unclosable if failures happen early in
+  initialization process. (#57)
+
+* Added support for transparent splash window. (#92)
+
+* Fixed problem with unpacked code resources (`ucode`) and `pack.gz` files. (#95)
+
+* Changed default Java version regex to support new Java 9+ version formats. (#93)
+
+* Ensure correct signature algorithm is used for each version of digest files. (#91)
+
+* Use more robust delete in all cases where Getdown needs to delete files. This should fix issues
+  with lingering files on Windows (where sometimes delete fails spuriously).
+
+## 1.7.0 - Dec 12, 2017
+
+* Fixed issue with `Digester` thread pool not being shutdown. (#89)
+
+* Fixed resource unpacking, which was broken by earlier change introducing resource installation
+  (downloading to `_new` files and then renaming into place). (#88)
+
+* The connect and read timeouts specified by system properties are now used for all the various
+  connections made by Getdown.
+
+* Proxy detection now uses a 5 second connect/read timeout, to avoid stalling for a long time in
+  certain problematic network conditions.
+
+* Getdown is now built against JDK 1.7 and requires JDK 1.7 (or newer) to run. Use the latest
+  Getdown 1.6.x release if you need to support Java 1.6.
+
+## 1.6.4 - Sep 17, 2017
+
+* `digest.txt` (and `digest2.txt`) computation now uses parallel jobs. Each resource to be verified
+  is a single job and the jobs are doled out to a thread pool with #CPUs threads. This allows large
+  builds to proceed faster as most dev machines have more than one core.
+
+* Resource verification is now performed in parallel (similar to the `digest.txt` computation, each
+  resource is a job farmed out to a thread pool). For large installations on multi-core machines,
+  this speeds up the verification phase of an installation or update.
+
+* Socket reads now have a 30 second default timeout. This can be changed by passing
+  `-Dread_timeout=N` (where N is seconds) to the JVM running Getdown.
+
+* Fixed issue with failing to install a downloaded and validated `_new` file.
+
+* Added support for "strict comments". In this mode, Getdown only treats `#` as starting a comment
+  if it appears in column zero. This allows `#` to occur on the right hand side of configuration
+  values (like in file names). To enable, put `strict_comments = true` in your `getdown.txt` file.
+
+## 1.6.3 - Apr 23, 2017
+
+* Fixed error parsing `cache_retention_days`. (#82)
+
+* Fixed error with new code cache. (9e23a426)
+
+## 1.6.2 - Feb 12, 2017
+
+* Fixed issue with installing local JVM, caused by new resource installation process. (#78)
+
+* Local JVM now uses absolute path to avoid issues with cwd.
+
+* Added `override_appbase` system property. This enables a Getdown app that normally talks to some
+  download server to be installed in such a way that it instead talks to some other download
+  server.
+
+## 1.6.1 - Feb 12, 2017
+
+* Fix issues with URL path encoding when downloading resources. (84af080b0)
+
+* Parsing `digest.txt` changed to allow `=` to appear in the filename. In `getdown.txt` we split on
+  the first `=` because `=` never appears in a key but may appear in a value. But in `digest.txt`
+  the format is `filename = hash` and `=` never appears in the hash but may appear in the filename,
+  so there we want to split on the _last_ `=` not the first.
+
+* Fixed bug with progress tracking and reporting. (256e0933)
+
+* Fix executable permissions on `jspawnhelper`. (#74)
+
+## 1.6 - Nov 5, 2016
+
+* This release and all those before it are considered ancient history. Check the commit history for
+  more details on what was in each of these releases.
+
+## 1.0 - Sep 21, 2010
+
+* The first Maven release of Getdown.
+
+## 0.1 - July 19, 2004
+
+* The first production use of Getdown (on https://www.puzzlepirates.com which is miraculously still
+  operational as of 2018 when this changelog was created).
+
+[migrating from 1.7 to 1.8]: https://github.com/threerings/getdown/wiki/Migrate17to18
diff --git a/getdown/src/getdown/LICENSE b/getdown/src/getdown/LICENSE
new file mode 100644 (file)
index 0000000..0d9b255
--- /dev/null
@@ -0,0 +1,24 @@
+Getdown - application installer, patcher and launcher
+
+Copyright (C) 2004-2016 Getdown authors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
+EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
diff --git a/getdown/src/getdown/README.md b/getdown/src/getdown/README.md
new file mode 100644 (file)
index 0000000..7059c61
--- /dev/null
@@ -0,0 +1,111 @@
+## What is it?
+
+Getdown (yes, it's the funky stuff) is a system for deploying Java applications to end-user
+computers, as well as keeping those applications up to date.
+
+It was designed as a replacement for [Java Web Start](https://docs.oracle.com/javase/8/docs/technotes/guides/javaws/)
+due to limitations in Java Web Start's architecture which are outlined in the
+[rationale](https://github.com/threerings/getdown/wiki/Rationale) section.
+
+Note: Getdown was designed *in 2004* as an alternative to Java Web Start, because of design choices
+made by JWS that were problematic to the use cases its authors had. It is _not_ a drop-in
+replacement for JWS, aimed to help the developers left in the lurch by the deprecation of JWS in
+Java 9. It may still be a viable alternative for developers looking to replace JWS, but don't
+expect to find feature parity with JWS.
+
+## How do I use it?
+
+A tutorial and more detailed specification are available from the [Documentation] page. Questions
+can be posted to the [OOO Libs Google group].
+
+Note that because one can not rely on users having a JRE installed, you must create a custom
+installer for each platform that you plan to support (Windows, macOS, Linux) that installs a JRE,
+the Getdown launcher jar file, a stub configuration file that identifies the URL at which your real
+app manifest is hosted, and whatever the appropiate "desktop integration" is that provides an icon
+the user can click on. We have some details on the
+[installers](https://github.com/threerings/getdown/wiki/Installers) documentation page, though it
+is unfortunately not very detailed.
+
+## How does it work?
+
+The main design and operation of Getdown is detailed on the
+[design](https://github.com/threerings/getdown/wiki/Design) page. You can also browse the
+[javadoc documentation] and [source code] if you're interested in implementation details.
+
+## Where can I see it in action?
+
+Getdown was originally written by developers at [OOO] for the deployment of their Java-based
+massively multiplayer games. Try out any of the following games to see it in action:
+
+  * [Puzzle Pirates](https://www.puzzlepirates.com/) - OOO
+  * [Spiral Knights](https://www.spiralknights.com/) - OOO
+
+Getdown is implemented in Java, and is designed to deploy and update JVM-based applications. While
+it would be technically feasible to use Getdown to deploy non-JVM-based applications, it is not
+currently supported and it is unlikely that the overhead of bundling a JVM just to run Getdown
+would be worth it if the JVM were not also being used to run the target application.
+
+## Release notes
+
+See [CHANGELOG.md](CHANGELOG.md) for release notes.
+
+## Obtaining Getdown
+
+Getdown will likely need to be integrated into your build. We have separate instructions for
+[build integration]. You can also download the individual jar files from Maven Central if needed.
+Getdown is comprised of three Maven artifacts (jar files), though you probably only need the first
+one:
+
+  * [getdown-launcher](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-launcher)
+    contains minified (via Proguard) code that you actually run to update and launch your app. It
+    also contains the tools needed to build a Getdown app distribution.
+
+  * [getdown-core](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-core) contains the
+    core logic for downloading, verifying, patching and launching an app as well as the core logic
+    for creating an app distribution. It does not contain any user interface code. You would only
+    use this artifact if you were planning to integrate Getdown directly into your app.
+
+  * [getdown-ant](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-ant) contains an Ant
+    task for building a Getdown app distribution. See the [build integration] instructions for
+    details.
+
+You can also:
+
+  * [Check out the code](https://github.com/threerings/getdown) and build it yourself.
+  * Browse the [source code] online.
+  * View the [javadoc documentation] online.
+
+## JVM Version Requirements
+
+  * Getdown version 1.8.x requires Java 7 VM or newer.
+  * Getdown version 1.7.x requires Java 7 VM or newer.
+  * Getdown version 1.6.x requires Java 6 VM or newer.
+  * Getdown version 1.5 and earlier requires Java 5 VM or newer.
+
+## Migrating from Getdown 1.7 to Getdown 1.8
+
+See [this document](https://github.com/threerings/getdown/wiki/Migrating-from-1.7-to-1.8) on the
+changes needed to migrate from Getdown 1.7 to 1.8.
+
+## Building
+
+Getdown is built with Maven in the standard ways. Invoke the following commands, for fun and
+profit:
+
+```
+% mvn compile  # builds the classes
+% mvn test     # builds and runs the unit tests
+% mvn package  # builds and creates jar file
+% mvn install  # builds, jars and installs in your local Maven repository
+```
+
+## Discussion
+
+Feel free to pop over to the [OOO Libs Google Group] to ask questions and get (and give) answers.
+
+[Documentation]: https://github.com/threerings/getdown/wiki
+[OOO Libs Google group]: http://groups.google.com/group/ooo-libs
+[source code]: https://github.com/threerings/getdown/tree/master/src/main/java/com/threerings/getdown/launcher
+[javadoc documentation]: https://threerings.github.com/getdown/apidocs/
+[OOO]: https://en.wikipedia.org/wiki/Three_Rings_Design
+[build integration]: https://github.com/threerings/getdown/wiki/Build-Integration
diff --git a/getdown/src/getdown/ant/.project-MOVED b/getdown/src/getdown/ant/.project-MOVED
new file mode 100644 (file)
index 0000000..097cb89
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>getdown-ant</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..e9441bb
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding/<project>=UTF-8
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..54e5672
--- /dev/null
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs
new file mode 100644 (file)
index 0000000..f897a7f
--- /dev/null
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/getdown/src/getdown/ant/pom.xml b/getdown/src/getdown/ant/pom.xml
new file mode 100644 (file)
index 0000000..f8231aa
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.threerings.getdown</groupId>
+    <artifactId>getdown</artifactId>
+    <version>1.8.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>getdown-ant</artifactId>
+  <packaging>jar</packaging>
+  <name>Getdown Ant Task</name>
+  <description>An Ant task for building Getdown app distributions</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.threerings.getdown</groupId>
+      <artifactId>getdown-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.7.1</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java
new file mode 100644 (file)
index 0000000..48cc8d4
--- /dev/null
@@ -0,0 +1,94 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.security.GeneralSecurityException;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import com.threerings.getdown.data.Digest;
+
+/**
+ * An ant task used to create a <code>digest.txt</code> for a Getdown
+ * application deployment.
+ */
+public class DigesterTask extends Task
+{
+    /**
+     * Sets the application directory.
+     */
+    public void setAppdir (File appdir)
+    {
+        _appdir = appdir;
+    }
+
+    /**
+     * Sets the digest signing keystore.
+     */
+    public void setKeystore (File path)
+    {
+        _storepath = path;
+    }
+
+    /**
+     * Sets the keystore decryption key.
+     */
+    public void setStorepass (String password)
+    {
+        _storepass = password;
+    }
+
+    /**
+     * Sets the private key alias.
+     */
+    public void setAlias (String alias)
+    {
+        _storealias = alias;
+    }
+
+    /**
+     * Performs the actual work of the task.
+     */
+    @Override
+    public void execute () throws BuildException
+    {
+        // make sure appdir is set
+        if (_appdir == null) {
+            throw new BuildException("Must specify the path to the application directory " +
+                                     "via the 'appdir' attribute.");
+        }
+
+        // make sure _storepass and _keyalias are set, if _storepath is set
+        if (_storepath != null && (_storepass == null || _storealias == null)) {
+            throw new BuildException(
+                    "Must specify both a keystore password and a private key alias.");
+        }
+
+        try {
+            Digester.createDigests(_appdir, _storepath, _storepass, _storealias);
+        } catch (IOException ioe) {
+            throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe);
+        } catch (GeneralSecurityException gse) {
+            throw new BuildException("Error creating signature: " + gse.getMessage(), gse);
+        }
+    }
+
+    /** The application directory in which we're creating a digest file. */
+    protected File _appdir;
+
+    /** The path to the keystore we'll use to sign the digest file, if any. */
+    protected File _storepath;
+
+    /** The decryption key for the keystore. */
+    protected String _storepass;
+
+    /** The private key alias. */
+    protected String _storealias;
+}
diff --git a/getdown/src/getdown/bin/differ b/getdown/src/getdown/bin/differ
new file mode 100755 (executable)
index 0000000..f48ed89
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar
+java -classpath */getdown*.jar com.threerings.getdown.tools.Differ "$@"
diff --git a/getdown/src/getdown/bin/patcher b/getdown/src/getdown/bin/patcher
new file mode 100755 (executable)
index 0000000..e09f67d
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar
+java -classpath */getdown*.jar com.threerings.getdown.tools.Patcher "$@"
diff --git a/getdown/src/getdown/core/.project-MOVED b/getdown/src/getdown/core/.project-MOVED
new file mode 100644 (file)
index 0000000..177252f
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>getdown-core</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..0a9bbb8
--- /dev/null
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/it/java=UTF-8
+encoding//src/main/java=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..54e5672
--- /dev/null
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs
new file mode 100644 (file)
index 0000000..f897a7f
--- /dev/null
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/getdown/src/getdown/core/pom.xml b/getdown/src/getdown/core/pom.xml
new file mode 100644 (file)
index 0000000..efec8b6
--- /dev/null
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.threerings.getdown</groupId>
+    <artifactId>getdown</artifactId>
+    <version>1.8.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>getdown-core</artifactId>
+  <packaging>jar</packaging>
+  <name>Getdown Core</name>
+  <description>Core Getdown functionality</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>2.22.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <!-- By default, no host whitelist is added to the binary, so it can be used
+       to download and run applications from any server. To create a custom
+       Getdown build that can only talk to whitelisted servers, set this
+       property on the command line, e.g. -Dgetdown.host.whitelist=my.server.com
+       Wildcards can be used (*.mycompany.com) and multiple values can be
+       separated by commas (app1.foo.com,app2.bar.com,app3.baz.com). -->
+  <properties>
+    <getdown.host.whitelist>jalview.org,*.jalview.org</getdown.host.whitelist>
+  </properties>
+
+  <build>
+    <resources>
+      <resource> <!-- include the LICENSE file in the jar -->
+        <directory>..</directory>
+        <includes><include>LICENSE</include></includes>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <version>1.5</version>
+        <executions>
+          <execution>
+            <id>add-test-source</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>add-test-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>src/it/java</source>
+              </sources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.8</version>
+        <executions>
+          <execution>
+            <id>gen-build</id>
+            <phase>generate-sources</phase>
+            <configuration>
+              <target>
+                <tstamp>
+                  <format property="getdown.build.time" pattern="yyyy-MM-dd HH:mm" />
+                </tstamp>
+                <copy file="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java.tmpl" tofile="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java" overwrite="true">
+                  <filterset>
+                    <filter token="build_time" value="${getdown.build.time}" />
+                    <filter token="build_version" value="${project.version}" />
+                    <filter token="host_whitelist" value="${getdown.host.whitelist}" />
+                  </filterset>
+                </copy>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-clean-plugin</artifactId>
+        <version>3.1.0</version>
+        <configuration>
+          <filesets>
+            <fileset>
+              <directory>${project.build.sourceDirectory}/</directory>
+              <includes>
+                <include>com/threerings/getdown/data/Build.java</include>
+              </includes>
+              <followSymlinks>false</followSymlinks>
+            </fileset>
+          </filesets>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>2.22.0</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <useFile>false</useFile>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java
new file mode 100644 (file)
index 0000000..52b4b5e
--- /dev/null
@@ -0,0 +1,54 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tests;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+import com.threerings.getdown.tools.Digester;
+
+public class DigesterIT {
+
+    @Test
+    public void testDigester () throws Exception {
+        Path appdir = Paths.get("src/it/resources/testapp");
+        Digester.createDigests(appdir.toFile(), null, null, null);
+
+        Path digest = appdir.resolve("digest.txt");
+        List<String> digestLines = Files.readAllLines(digest, StandardCharsets.UTF_8);
+        Files.delete(digest);
+
+        Path digest2 = appdir.resolve("digest2.txt");
+        List<String> digest2Lines = Files.readAllLines(digest2, StandardCharsets.UTF_8);
+        Files.delete(digest2);
+
+        assertEquals(Arrays.asList(
+            "getdown.txt = 779c74fb4b251e18faf6e240a0667964",
+            "testapp.jar = 404dafa55e78b25ec0e3a936357b1883",
+            "funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249",
+            "crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16",
+            "foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec",
+            "script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf",
+            "digest.txt = f5561d85e4d80cc85883963897e58ff6"
+        ), digestLines);
+
+        assertEquals(Arrays.asList(
+            "getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b",
+            "testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0",
+            "funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
+            "crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b",
+            "foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062",
+            "script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988",
+            "digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407"
+        ), digest2Lines);
+    }
+}
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/background.png b/getdown/src/getdown/core/src/it/resources/testapp/background.png
new file mode 100644 (file)
index 0000000..ff6a6ee
Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/background.png differ
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt b/getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt
new file mode 100644 (file)
index 0000000..33bc373
--- /dev/null
@@ -0,0 +1 @@
+Hello crazy world.
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/foo.jar b/getdown/src/getdown/core/src/it/resources/testapp/foo.jar
new file mode 100644 (file)
index 0000000..d040c01
Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/foo.jar differ
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt b/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt
new file mode 100644 (file)
index 0000000..9daeafb
--- /dev/null
@@ -0,0 +1 @@
+test
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt
new file mode 100644 (file)
index 0000000..3e0e538
--- /dev/null
@@ -0,0 +1,28 @@
+# where our app is hosted on the internets
+appbase = http://notused.com/testapp
+
+# the jar file that contains our code
+code = testapp.jar
+
+# the main entry point of our app
+class = com.threerings.testapp.TestApp
+
+# we pass the appdir to our app so that it can upgrade getdown
+apparg = %APPDIR%
+
+# test the %env% mechanism
+jvmarg = -Dusername=\%ENV.USER%
+
+strict_comments = true
+resource = funny%test dir/some=file.txt
+resource = crazyhashfile#txt
+uresource = foo.jar
+xresource = script.sh
+
+ui.name = Getdown Test App
+ui.background_image = background.png
+ui.progress = 17, 321, 458, 22
+ui.progress_bar = 336600
+ui.progress_text = FFFFFF
+ui.status = 57, 245, 373, 68
+ui.status_text = 000000
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/script.sh b/getdown/src/getdown/core/src/it/resources/testapp/script.sh
new file mode 100644 (file)
index 0000000..e3a1aba
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "Hello world!"
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar b/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar
new file mode 100644 (file)
index 0000000..fe9de02
Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar differ
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java
new file mode 100644 (file)
index 0000000..13b9956
--- /dev/null
@@ -0,0 +1,141 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.*;
+
+/**
+ * A placeholder class that contains a reference to the log object used by the Getdown code.
+ */
+public class Log
+{
+    public static class Shim {
+        /**
+         * Logs a debug message.
+         *
+         * @param message the message to be logged.
+         * @param args a list of key/value pairs and an optional final Throwable.
+         */
+        public void debug (Object message, Object... args) { doLog(0, message, args); }
+
+        /**
+         * Logs an info message.
+         *
+         * @param message the message to be logged.
+         * @param args a list of key/value pairs and an optional final Throwable.
+         */
+        public void info (Object message, Object... args) { doLog(1, message, args); }
+
+        /**
+         * Logs a warning message.
+         *
+         * @param message the message to be logged.
+         * @param args a list of key/value pairs and an optional final Throwable.
+         */
+        public void warning (Object message, Object... args) { doLog(2, message, args); }
+
+        /**
+         * Logs an error message.
+         *
+         * @param message the message to be logged.
+         * @param args a list of key/value pairs and an optional final Throwable.
+         */
+        public void error (Object message, Object... args) { doLog(3, message, args); }
+
+        protected void doLog (int levIdx, Object message, Object[] args) {
+            if (_impl.isLoggable(LEVELS[levIdx])) {
+                Throwable err = null;
+                int nn = args.length;
+                if (message instanceof Throwable) {
+                    err = (Throwable)message;
+                } else if (nn % 2 == 1 && (args[nn - 1] instanceof Throwable)) {
+                    err = (Throwable)args[--nn];
+                }
+                _impl.log(LEVELS[levIdx], format(message, args), err);
+            }
+        }
+
+        protected final Logger _impl = Logger.getLogger("com.threerings.getdown");
+    }
+
+    /** We dispatch our log messages through this logging shim. */
+    public static final Shim log = new Shim();
+
+    public static String format (Object message, Object... args) {
+        if (args.length < 2) return String.valueOf(message);
+        StringBuilder buf = new StringBuilder(String.valueOf(message));
+        if (buf.length() > 0) {
+            buf.append(' ');
+        }
+        buf.append('[');
+        for (int ii = 0; ii < args.length; ii += 2) {
+            if (ii > 0) {
+                buf.append(',').append(' ');
+            }
+            buf.append(args[ii]).append('=');
+            try {
+                buf.append(args[ii+1]);
+            } catch (Throwable t) {
+                buf.append("<toString() failure: ").append(t).append(">");
+            }
+        }
+        return buf.append(']').toString();
+    }
+
+    static {
+        Formatter formatter = new OneLineFormatter();
+        Logger logger = LogManager.getLogManager().getLogger("");
+        for (Handler handler : logger.getHandlers()) {
+            handler.setFormatter(formatter);
+        }
+    }
+
+    protected static class OneLineFormatter extends Formatter {
+        @Override public String format (LogRecord record) {
+            StringBuffer buf = new StringBuffer();
+
+            // append the timestamp
+            _date.setTime(record.getMillis());
+            _format.format(_date, buf, _fpos);
+
+            // append the log level
+            buf.append(" ");
+            buf.append(record.getLevel().getLocalizedName());
+            buf.append(" ");
+
+            // append the message itself
+            buf.append(formatMessage(record));
+            buf.append(System.lineSeparator());
+
+            // if an exception was also provided, append that
+            if (record.getThrown() != null) {
+                try {
+                    StringWriter sw = new StringWriter();
+                    PrintWriter pw = new PrintWriter(sw);
+                    record.getThrown().printStackTrace(pw);
+                    pw.close();
+                    buf.append(sw.toString());
+                } catch (Exception ex) {
+                    buf.append("Format failure:").append(ex);
+                }
+            }
+
+            return buf.toString();
+        }
+
+        protected Date _date = new Date();
+        protected SimpleDateFormat _format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS");
+        protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD);
+    }
+
+    protected static final String DATE_FORMAT = "{0,date} {0,time}";
+    protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE};
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java
new file mode 100644 (file)
index 0000000..67ea645
--- /dev/null
@@ -0,0 +1,99 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.cache;
+
+import java.io.File;
+import com.threerings.getdown.util.FileUtil;
+
+/**
+ * Collects elements in the {@link ResourceCache cache} which became unused and deletes them
+ * afterwards.
+ */
+public class GarbageCollector
+{
+    /**
+     * Collect and delete the garbage in the cache.
+     */
+    public static void collect (File cacheDir, final long retentionPeriodMillis)
+    {
+        FileUtil.walkTree(cacheDir, new FileUtil.Visitor() {
+            @Override public void visit (File file) {
+                File cachedFile = getCachedFile(file);
+                File lastAccessedFile = getLastAccessedFile(file);
+                if (!cachedFile.exists() || !lastAccessedFile.exists()) {
+                    if (cachedFile.exists()) {
+                        FileUtil.deleteHarder(cachedFile);
+                    } else {
+                        FileUtil.deleteHarder(lastAccessedFile);
+                    }
+                } else if (shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
+                    FileUtil.deleteHarder(lastAccessedFile);
+                    FileUtil.deleteHarder(cachedFile);
+                }
+
+                File folder = file.getParentFile();
+                if (folder != null) {
+                    String[] children = folder.list();
+                    if (children != null && children.length == 0) {
+                        FileUtil.deleteHarder(folder);
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Collect and delete garbage in the native cache. It tries to find a jar file with a matching
+     * last modified file, and deletes the entire directory accordingly.
+     */
+    public static void collectNative (File cacheDir, final long retentionPeriodMillis)
+    {
+        File[] subdirs = cacheDir.listFiles();
+        if (subdirs != null) {
+            for (File dir : subdirs) {
+                if (dir.isDirectory()) {
+                    // Get all the native jars in the directory (there should only be one)
+                    for (File file : dir.listFiles()) {
+                        if (!file.getName().endsWith(".jar")) {
+                            continue;
+                        }
+                        File cachedFile = getCachedFile(file);
+                        File lastAccessedFile = getLastAccessedFile(file);
+                        if (!cachedFile.exists() || !lastAccessedFile.exists() ||
+                            shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
+                            FileUtil.deleteDirHarder(dir);
+                        }
+                    }
+                } else {
+                    // @TODO There shouldn't be any loose files in native/ but if there are then
+                    // what? Delete them? file.delete();
+                }
+            }
+        }
+    }
+
+    private static boolean shouldDelete (File lastAccessedFile, long retentionMillis)
+    {
+        return System.currentTimeMillis() - lastAccessedFile.lastModified() > retentionMillis;
+    }
+
+    private static File getLastAccessedFile (File file)
+    {
+        return isLastAccessedFile(file) ? file : new File(
+            file.getParentFile(), file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
+    }
+
+    private static boolean isLastAccessedFile (File file)
+    {
+        return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
+    }
+
+    private static File getCachedFile (File file)
+    {
+        return !isLastAccessedFile(file) ? file : new File(
+            file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")));
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java
new file mode 100644 (file)
index 0000000..0210e9a
--- /dev/null
@@ -0,0 +1,80 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.cache;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.threerings.getdown.util.FileUtil;
+
+/**
+ * Maintains a cache of code resources. The cache allows multiple application instances of different
+ * versions to open at the same time.
+ */
+public class ResourceCache
+{
+    public ResourceCache (File _cacheDir) throws IOException
+    {
+        this._cacheDir = _cacheDir;
+        createDirectoryIfNecessary(_cacheDir);
+    }
+
+    private void createDirectoryIfNecessary (File dir) throws IOException
+    {
+        if (!dir.exists() && !dir.mkdirs()) {
+            throw new IOException("unable to create directory: " + dir.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Caches the given file under its {@code digest}.
+     * @param fileToCache file to cache.
+     * @param cacheSubdir the subdirectory of the cache directory in which to store the cached
+     * file. Usually either {@code digest} or a prefix of {@code digest}.
+     * @param digest a crypto digest of the cached files contents.
+     * @return the cached file.
+     */
+    public File cacheFile (File fileToCache, String cacheSubdir, String digest) throws IOException
+    {
+        File cacheLocation = new File(_cacheDir, cacheSubdir);
+        createDirectoryIfNecessary(cacheLocation);
+
+        File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache));
+        File lastAccessedFile = new File(
+                cacheLocation, cachedFile.getName() + LAST_ACCESSED_FILE_SUFFIX);
+
+        if (!cachedFile.exists()) {
+            createNewFile(cachedFile);
+            FileUtil.copy(fileToCache, cachedFile);
+        }
+
+        if (lastAccessedFile.exists()) {
+            lastAccessedFile.setLastModified(System.currentTimeMillis());
+        } else {
+            createNewFile(lastAccessedFile);
+        }
+
+        return cachedFile;
+    }
+
+    private void createNewFile (File fileToCreate) throws IOException
+    {
+        if (!fileToCreate.exists() && !fileToCreate.createNewFile()) {
+            throw new IOException("unable to create new file: " + fileToCreate.getAbsolutePath());
+        }
+    }
+
+    private String getFileSuffix (File fileToCache) {
+        String fileName = fileToCache.getName();
+        int index = fileName.lastIndexOf(".");
+
+        return index > -1 ? fileName.substring(index) : "";
+    }
+
+    private final File _cacheDir;
+
+    static final String LAST_ACCESSED_FILE_SUFFIX = ".lastAccessed";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
new file mode 100644 (file)
index 0000000..25cd109
--- /dev/null
@@ -0,0 +1,1956 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+import com.sun.management.OperatingSystemMXBean;
+import java.lang.management.ManagementFactory;
+
+
+import com.threerings.getdown.util.*;
+// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
+import com.threerings.getdown.util.Base64;
+
+import com.threerings.getdown.data.EnvConfig;
+import com.threerings.getdown.data.EnvConfig.Note;
+
+import static com.threerings.getdown.Log.log;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Parses and provide access to the information contained in the <code>getdown.txt</code>
+ * configuration file.
+ */
+public class Application
+{
+    /** The name of our configuration file. */
+    public static final String CONFIG_FILE = "getdown.txt";
+
+    /** The name of our target version file. */
+    public static final String VERSION_FILE = "version.txt";
+
+    /** System properties that are prefixed with this string will be passed through to our
+     * application (minus this prefix). */
+    public static final String PROP_PASSTHROUGH_PREFIX = "app.";
+
+    /** Suffix used for control file signatures. */
+    public static final String SIGNATURE_SUFFIX = ".sig";
+
+    /** A special classname that means 'use -jar code.jar' instead of a classname. */
+    public static final String MANIFEST_CLASS = "manifest";
+
+    /** Used to communicate information about the UI displayed when updating the application. */
+    public static final class UpdateInterface
+    {
+        /**
+         * The major steps involved in updating, along with some arbitrary percentages
+         * assigned to them, to mark global progress.
+         */
+        public enum Step
+        {
+            UPDATE_JAVA(10),
+            VERIFY_METADATA(15, 65, 95),
+            DOWNLOAD(40),
+            PATCH(60),
+            VERIFY_RESOURCES(70, 97),
+            REDOWNLOAD_RESOURCES(90),
+            UNPACK(98),
+            LAUNCH(99);
+
+            /** What is the final percent value for this step? */
+            public final List<Integer> defaultPercents;
+
+            /** Enum constructor. */
+            Step (int... percents)
+            {
+                this.defaultPercents = intsToList(percents);
+            }
+        }
+
+        /** The human readable name of this application. */
+        public final String name;
+
+        /** A background color, just in case. */
+        public final int background;
+
+        /** Background image specifiers for `RotatingBackgrounds`. */
+        public final List<String> rotatingBackgrounds;
+
+        /** The error background image for `RotatingBackgrounds`. */
+        public final String errorBackground;
+
+        /** The paths (relative to the appdir) of images for the window icon. */
+        public final List<String> iconImages;
+
+        /** The path (relative to the appdir) to a single background image. */
+        public final String backgroundImage;
+
+        /** The path (relative to the appdir) to the progress bar image. */
+        public final String progressImage;
+
+        /** The dimensions of the progress bar. */
+        public final Rectangle progress;
+
+        /** The color of the progress text. */
+        public final int progressText;
+
+        /** The color of the progress bar. */
+        public final int progressBar;
+
+        /** The dimensions of the status display. */
+        public final Rectangle status;
+
+        /** The color of the status text. */
+        public final int statusText;
+
+        /** The color of the text shadow. */
+        public final int textShadow;
+
+        /** Where to point the user for help with install errors. */
+        public final String installError;
+
+        /** The dimensions of the patch notes button. */
+        public final Rectangle patchNotes;
+
+        /** The patch notes URL. */
+        public final String patchNotesUrl;
+
+        /** Whether window decorations are hidden for the UI. */
+        public final boolean hideDecorations;
+
+        /** Whether progress text should be hidden or not. */
+        public final boolean hideProgressText;
+
+        /** The minimum number of seconds to display the GUI. This is to prevent the GUI from
+          * flashing up on the screen and immediately disappearing, which can be confusing to the
+          * user. */
+        public final int minShowSeconds;
+
+        /** The global percentages for each step. A step may have more than one, and
+         * the lowest reasonable one is used if a step is revisited. */
+        public final Map<Step, List<Integer>> stepPercentages;
+
+        /** Generates a string representation of this instance. */
+        @Override
+        public String toString ()
+        {
+            return "[name=" + name + ", bg=" + background + ", bg=" + backgroundImage +
+                ", pi=" + progressImage + ", prect=" + progress + ", pt=" + progressText +
+                ", pb=" + progressBar + ", srect=" + status + ", st=" + statusText +
+                ", shadow=" + textShadow + ", err=" + installError + ", nrect=" + patchNotes +
+                ", notes=" + patchNotesUrl + ", stepPercentages=" + stepPercentages +
+                ", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]";
+        }
+
+        public UpdateInterface (Config config)
+        {
+            this.name = config.getString("ui.name");
+            this.progress = config.getRect("ui.progress", new Rectangle(5, 5, 300, 15));
+            this.progressText = config.getColor("ui.progress_text", Color.BLACK);
+            this.hideProgressText =  config.getBoolean("ui.hide_progress_text");
+            this.minShowSeconds = config.getInt("ui.min_show_seconds", 5);
+            this.progressBar = config.getColor("ui.progress_bar", 0x6699CC);
+            this.status = config.getRect("ui.status", new Rectangle(5, 25, 500, 100));
+            this.statusText = config.getColor("ui.status_text", Color.BLACK);
+            this.textShadow = config.getColor("ui.text_shadow", Color.CLEAR);
+            this.hideDecorations = config.getBoolean("ui.hide_decorations");
+            this.backgroundImage = config.getString("ui.background_image");
+            // default to black or white bg color, depending on the brightness of the progressText
+            int defaultBackground = (0.5f < Color.brightness(this.progressText)) ?
+                Color.BLACK : Color.WHITE;
+            this.background = config.getColor("ui.background", defaultBackground);
+            this.progressImage = config.getString("ui.progress_image");
+            this.rotatingBackgrounds = stringsToList(
+                config.getMultiValue("ui.rotating_background"));
+            this.iconImages = stringsToList(config.getMultiValue("ui.icon"));
+            this.errorBackground = config.getString("ui.error_background");
+
+            // On an installation error, where do we point the user.
+            String installError = config.getUrl("ui.install_error", null);
+            this.installError = (installError == null) ?
+                "m.default_install_error" : MessageUtil.taint(installError);
+
+            // the patch notes bits
+            this.patchNotes = config.getRect("ui.patch_notes", new Rectangle(5, 50, 112, 26));
+            this.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
+
+            // step progress percentage (defaults and then customized values)
+            EnumMap<Step, List<Integer>> stepPercentages = new EnumMap<>(Step.class);
+            for (Step step : Step.values()) {
+                stepPercentages.put(step, step.defaultPercents);
+            }
+            for (UpdateInterface.Step step : UpdateInterface.Step.values()) {
+                String spec = config.getString("ui.percents." + step.name());
+                if (spec != null) {
+                    try {
+                        stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec)));
+                    } catch (Exception e) {
+                        log.warning("Failed to parse percentages for " + step + ": " + spec);
+                    }
+                }
+            }
+            this.stepPercentages = Collections.unmodifiableMap(stepPercentages);
+        }
+    }
+
+    /**
+     * Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to
+     * take network actions.
+     */
+    public static interface StatusDisplay
+    {
+        /** Requests that the specified status message be displayed. */
+        public void updateStatus (String message);
+    }
+
+    /**
+     * Contains metadata for an auxiliary resource group.
+     */
+    public static class AuxGroup {
+        public final String name;
+        public final List<Resource> codes;
+        public final List<Resource> rsrcs;
+
+        public AuxGroup (String name, List<Resource> codes, List<Resource> rsrcs) {
+            this.name = name;
+            this.codes = Collections.unmodifiableList(codes);
+            this.rsrcs = Collections.unmodifiableList(rsrcs);
+        }
+    }
+
+    /** The proxy that should be used to do HTTP downloads. This must be configured prior to using
+      * the application instance. Yes this is a public mutable field, no I'm not going to create a
+      * getter and setter just to pretend like that's not the case. */
+    public Proxy proxy = Proxy.NO_PROXY;
+
+    /**
+     * Creates an application instance which records the location of the <code>getdown.txt</code>
+     * configuration file from the supplied application directory.
+     *
+     */
+    public Application (EnvConfig envc) {
+        _envc = envc;
+        _config = getLocalPath(envc.appDir, CONFIG_FILE);
+    }
+
+    /**
+     * Returns the configured application directory.
+     */
+    public File getAppDir () {
+        return _envc.appDir;
+    }
+
+    /**
+     * Returns whether the application should cache code resources prior to launching the
+     * application.
+     */
+    public boolean useCodeCache ()
+    {
+        return _useCodeCache;
+    }
+
+    /**
+     * Returns the number of days a cached code resource is allowed to stay unused before it
+     * becomes eligible for deletion.
+     */
+    public int getCodeCacheRetentionDays ()
+    {
+        return _codeCacheRetentionDays;
+    }
+
+    /**
+     * Returns the configured maximum concurrent downloads. Used to cap simultaneous downloads of
+     * app files from its hosting server.
+     */
+    public int maxConcurrentDownloads () {
+        return _maxConcDownloads;
+    }
+
+    /**
+     * Returns a resource that refers to the application configuration file itself.
+     */
+    public Resource getConfigResource ()
+    {
+        try {
+            return createResource(CONFIG_FILE, Resource.NORMAL);
+        } catch (Exception e) {
+            throw new RuntimeException("Invalid appbase '" + _vappbase + "'.", e);
+        }
+    }
+
+    /**
+     * Returns a list of the code {@link Resource} objects used by this application.
+     */
+    public List<Resource> getCodeResources ()
+    {
+        return _codes;
+    }
+
+    /**
+     * Returns a list of the non-code {@link Resource} objects used by this application.
+     */
+    public List<Resource> getResources ()
+    {
+        return _resources;
+    }
+
+    /**
+     * Returns the digest of the given {@code resource}.
+     */
+    public String getDigest (Resource resource)
+    {
+        return _digest.getDigest(resource);
+    }
+
+    /**
+     * Returns a list of all the active {@link Resource} objects used by this application (code and
+     * non-code).
+     */
+    public List<Resource> getAllActiveResources ()
+    {
+        List<Resource> allResources = new ArrayList<>();
+        allResources.addAll(getActiveCodeResources());
+        allResources.addAll(getActiveResources());
+        return allResources;
+    }
+
+    /**
+     * Returns the auxiliary resource group with the specified name, or null.
+     */
+    public AuxGroup getAuxGroup (String name)
+    {
+        return _auxgroups.get(name);
+    }
+
+    /**
+     * Returns the set of all auxiliary resource groups defined by the application. An auxiliary
+     * resource group is a collection of resource files that are not downloaded unless a group
+     * token file is present in the application directory.
+     */
+    public Iterable<AuxGroup> getAuxGroups ()
+    {
+        return _auxgroups.values();
+    }
+
+    /**
+     * Returns true if the specified auxgroup has been "activated", false if not. Non-activated
+     * groups should be ignored, activated groups should be downloaded and patched along with the
+     * main resources.
+     */
+    public boolean isAuxGroupActive (String auxgroup)
+    {
+        Boolean active = _auxactive.get(auxgroup);
+        if (active == null) {
+            // TODO: compare the contents with the MD5 hash of the auxgroup name and the client's
+            // machine ident
+            active = getLocalPath(auxgroup + ".dat").exists();
+            _auxactive.put(auxgroup, active);
+        }
+        return active;
+    }
+
+    /**
+     * Returns all main code resources and all code resources from active auxiliary resource groups.
+     */
+    public List<Resource> getActiveCodeResources ()
+    {
+        ArrayList<Resource> codes = new ArrayList<>();
+        codes.addAll(getCodeResources());
+        for (AuxGroup aux : getAuxGroups()) {
+            if (isAuxGroupActive(aux.name)) {
+                codes.addAll(aux.codes);
+            }
+        }
+        return codes;
+    }
+
+    /**
+     * Returns all resources indicated to contain native library files (.dll, .so, etc.).
+     */
+    public List<Resource> getNativeResources ()
+    {
+        List<Resource> natives = new ArrayList<>();
+        for (Resource resource: _resources) {
+            if (resource.isNative()) {
+                natives.add(resource);
+            }
+        }
+        return natives;
+    }
+
+    /**
+     * Returns all non-code resources and all resources from active auxiliary resource groups.
+     */
+    public List<Resource> getActiveResources ()
+    {
+        ArrayList<Resource> rsrcs = new ArrayList<>();
+        rsrcs.addAll(getResources());
+        for (AuxGroup aux : getAuxGroups()) {
+            if (isAuxGroupActive(aux.name)) {
+                rsrcs.addAll(aux.rsrcs);
+            }
+        }
+        return rsrcs;
+    }
+
+    /**
+     * Returns a resource that can be used to download a patch file that will bring this
+     * application from its current version to the target version.
+     *
+     * @param auxgroup the auxiliary resource group for which a patch resource is desired or null
+     * for the main application patch resource.
+     */
+    public Resource getPatchResource (String auxgroup)
+    {
+        if (_targetVersion <= _version) {
+            log.warning("Requested patch resource for up-to-date or non-versioned application",
+                "cvers", _version, "tvers", _targetVersion);
+            return null;
+        }
+
+        String infix = (auxgroup == null) ? "" : ("-" + auxgroup);
+        String pfile = "patch" + infix + _version + ".dat";
+        try {
+            URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile));
+            return new Resource(pfile, remote, getLocalPath(pfile), Resource.NORMAL);
+        } catch (Exception e) {
+            log.warning("Failed to create patch resource path",
+                "pfile", pfile, "appbase", _appbase, "tvers", _targetVersion, "error", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns a resource for a zip file containing a Java VM that can be downloaded to use in
+     * place of the installed VM (in the case where the VM that launched Getdown does not meet the
+     * application's version requirements) or null if no VM is available for this platform.
+     */
+    public Resource getJavaVMResource ()
+    {
+        if (StringUtil.isBlank(_javaLocation)) {
+            return null;
+        }
+
+        String extension = (_javaLocation.endsWith(".tgz"))?".tgz":".jar";
+        String vmfile = LaunchUtil.LOCAL_JAVA_DIR + extension;
+               log.info("vmfile is '"+vmfile+"'");
+               System.out.println("vmfile is '"+vmfile+"'");
+        try {
+            URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation));
+            log.info("Attempting to fetch jvm at "+remote.toString());
+            System.out.println("Attempting to fetch jvm at "+remote.toString());
+            return new Resource(vmfile, remote, getLocalPath(vmfile),
+                                EnumSet.of(Resource.Attr.UNPACK, Resource.Attr.CLEAN));
+        } catch (Exception e) {
+            log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase,
+                "tvers", _targetVersion, "javaloc", _javaLocation, "error", e);
+            System.out.println("Failed to create VM resource: vmfile="+vmfile+", appbase="+_appbase+
+                ", tvers="+_targetVersion+", javaloc="+_javaLocation+", error="+e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns a resource that can be used to download an archive containing all files belonging to
+     * the application.
+     */
+    public Resource getFullResource ()
+    {
+        String file = "full";
+        try {
+            URL remote = new URL(createVAppBase(_targetVersion), encodePath(file));
+            return new Resource(file, remote, getLocalPath(file), Resource.NORMAL);
+        } catch (Exception e) {
+            log.warning("Failed to create full resource path",
+                "file", file, "appbase", _appbase, "tvers", _targetVersion, "error", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the URL to use to report an initial download event. Returns null if no tracking
+     * start URL was configured for this application.
+     *
+     * @param event the event to be reported: start, jvm_start, jvm_complete, complete.
+     */
+    public URL getTrackingURL (String event)
+    {
+        try {
+            String suffix = _trackingURLSuffix == null ? "" : _trackingURLSuffix;
+            String ga = getGATrackingCode();
+            return _trackingURL == null ? null :
+                HostWhitelist.verify(new URL(_trackingURL + encodePath(event + suffix + ga)));
+        } catch (MalformedURLException mue) {
+            log.warning("Invalid tracking URL", "path", _trackingURL, "event", event, "error", mue);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the URL to request to report that we have reached the specified percentage of our
+     * initial download. Returns null if no tracking request was configured for the specified
+     * percentage.
+     */
+    public URL getTrackingProgressURL (int percent)
+    {
+        if (_trackingPcts == null || !_trackingPcts.contains(percent)) {
+            return null;
+        }
+        return getTrackingURL("pct" + percent);
+    }
+
+    /**
+     * Returns the name of our tracking cookie or null if it was not set.
+     */
+    public String getTrackingCookieName ()
+    {
+        return _trackingCookieName;
+    }
+
+    /**
+     * Returns the name of our tracking cookie system property or null if it was not set.
+     */
+    public String getTrackingCookieProperty ()
+    {
+        return _trackingCookieProperty;
+    }
+
+    /**
+     * Instructs the application to parse its {@code getdown.txt} configuration and prepare itself
+     * for operation. The application base URL will be parsed first so that if there are errors
+     * discovered later, the caller can use the application base to download a new {@code
+     * getdown.txt} file and try again.
+     *
+     * @return a {@code Config} instance that contains information from the config file.
+     *
+     * @exception IOException thrown if there is an error reading the file or an error encountered
+     * during its parsing.
+     */
+    public Config init (boolean checkPlatform)
+        throws IOException
+    {
+        Config config = null;
+        File cfgfile = _config;
+        Config.ParseOpts opts = Config.createOpts(checkPlatform);
+        try {
+            // if we have a configuration file, read the data from it
+            if (cfgfile.exists()) {
+                config = Config.parseConfig(_config, opts);
+            }
+            // otherwise, try reading data from our backup config file; thanks to funny windows
+            // bullshit, we have to do this backup file fiddling in case we got screwed while
+            // updating getdown.txt during normal operation
+            else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) {
+                config = Config.parseConfig(cfgfile, opts);
+            }
+            // otherwise, issue a warning that we found no getdown file
+            else {
+                log.info("Found no getdown.txt file", "appdir", getAppDir());
+            }
+        } catch (Exception e) {
+            log.warning("Failure reading config file", "file", _config, e);
+        }
+        
+        // see if there's an override config from locator file
+        Config locatorConfig = createLocatorConfig(opts);
+        
+        // merge the locator file config into config (or replace config with)
+        if (locatorConfig != null) {
+          if (config == null || locatorConfig.getBoolean(LOCATOR_FILE_EXTENSION+"_replace")) {
+            config = locatorConfig;
+          } else {
+            config.mergeConfig(locatorConfig, locatorConfig.getBoolean(LOCATOR_FILE_EXTENSION+"_merge"));
+          }
+        }
+
+        // if we failed to read our config file, check for an appbase specified via a system
+        // property; we can use that to bootstrap ourselves back into operation
+        if (config == null) {
+            String appbase = _envc.appBase;
+            log.info("Using 'appbase' from bootstrap config", "appbase", appbase);
+            Map<String, Object> cdata = new HashMap<>();
+            cdata.put("appbase", appbase);
+            config = new Config(cdata);
+        }
+
+        // first determine our application base, this way if anything goes wrong later in the
+        // process, our caller can use the appbase to download a new configuration file
+        _appbase = config.getString("appbase");
+        
+        // see if locatorConfig override
+        if (locatorConfig != null && !StringUtil.isBlank(locatorConfig.getString("appbase"))) {
+          _appbase = locatorConfig.getString("appbase");
+        }
+        
+        if (_appbase == null) {
+            throw new RuntimeException("m.missing_appbase");
+        }
+
+        // check if we're overriding the domain in the appbase
+        _appbase = SysProps.overrideAppbase(_appbase);
+
+        // make sure there's a trailing slash
+        if (!_appbase.endsWith("/")) {
+            _appbase = _appbase + "/";
+        }
+
+        // extract our version information
+        _version = config.getLong("version", -1L);
+
+        // if we are a versioned deployment, create a versioned appbase
+        try {
+            _vappbase = createVAppBase(_version);
+        } catch (MalformedURLException mue) {
+            String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
+            throw (IOException) new IOException(err).initCause(mue);
+        }
+
+        // check for a latest config URL
+        String latest = config.getString("latest");
+        if (latest != null) {
+            if (latest.startsWith(_appbase)) {
+                latest = _appbase + latest.substring(_appbase.length());
+            } else {
+                latest = SysProps.replaceDomain(latest);
+            }
+            try {
+                _latest = HostWhitelist.verify(new URL(latest));
+            } catch (MalformedURLException mue) {
+                log.warning("Invalid URL for latest attribute.", mue);
+            }
+        }
+
+        String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
+
+        // determine our application class name (use app-specific class _if_ one is provided)
+        _class = config.getString("class");
+        if (appPrefix.length() > 0) {
+            _class = config.getString(appPrefix + "class", _class);
+        }
+        if (_class == null) {
+            throw new IOException("m.missing_class");
+        }
+
+        // determine whether we want strict comments
+        _strictComments = config.getBoolean("strict_comments");
+
+        // check to see if we're using a custom java.version property and regex
+        _javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
+        _javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
+
+        // check to see if we require a particular JVM version and have a supplied JVM
+        _javaMinVersion = config.getLong("java_version", _javaMinVersion);
+        // we support java_min_version as an alias of java_version; it better expresses the check
+        // that's going on and better mirrors java_max_version
+        _javaMinVersion = config.getLong("java_min_version", _javaMinVersion);
+        // check to see if we require a particular max JVM version and have a supplied JVM
+        _javaMaxVersion = config.getLong("java_max_version", _javaMaxVersion);
+        // check to see if we require a particular JVM version and have a supplied JVM
+        _javaExactVersionRequired = config.getBoolean("java_exact_version_required");
+
+        // this is a little weird, but when we're run from the digester, we see a String[] which
+        // contains java locations for all platforms which we can't grok, but the digester doesn't
+        // need to know about that; when we're run in a real application there will be only one!
+        Object javaloc = config.getRaw("java_location");
+        if (javaloc instanceof String) {
+            _javaLocation = (String)javaloc;
+        }
+
+        // determine whether we have any tracking configuration
+        _trackingURL = config.getString("tracking_url");
+
+        // check for tracking progress percent configuration
+        String trackPcts = config.getString("tracking_percents");
+        if (!StringUtil.isBlank(trackPcts)) {
+            _trackingPcts = new HashSet<>();
+            for (int pct : StringUtil.parseIntArray(trackPcts)) {
+                _trackingPcts.add(pct);
+            }
+        } else if (!StringUtil.isBlank(_trackingURL)) {
+            _trackingPcts = new HashSet<>();
+            _trackingPcts.add(50);
+        }
+
+        // Check for tracking cookie configuration
+        _trackingCookieName = config.getString("tracking_cookie_name");
+        _trackingCookieProperty = config.getString("tracking_cookie_property");
+
+        // Some app may need an extra suffix added to the tracking URL
+        _trackingURLSuffix = config.getString("tracking_url_suffix");
+
+        // Some app may need to generate google analytics code
+        _trackingGAHash = config.getString("tracking_ga_hash");
+
+        // clear our arrays as we may be reinitializing
+        _codes.clear();
+        _resources.clear();
+        _auxgroups.clear();
+        _jvmargs.clear();
+        _appargs.clear();
+        _txtJvmArgs.clear();
+
+        // parse our code resources
+        if (config.getMultiValue("code") == null &&
+            config.getMultiValue("ucode") == null) {
+            throw new IOException("m.missing_code");
+        }
+        parseResources(config, "code", Resource.NORMAL, _codes);
+        parseResources(config, "ucode", Resource.UNPACK, _codes);
+
+        // parse our non-code resources
+        parseResources(config, "resource", Resource.NORMAL, _resources);
+        parseResources(config, "uresource", Resource.UNPACK, _resources);
+        parseResources(config, "xresource", Resource.EXEC, _resources);
+        parseResources(config, "presource", Resource.PRELOAD, _resources);
+        parseResources(config, "nresource", Resource.NATIVE, _resources);
+
+        // parse our auxiliary resource groups
+        for (String auxgroup : config.getList("auxgroups")) {
+            ArrayList<Resource> codes = new ArrayList<>();
+            parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
+            parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
+            ArrayList<Resource> rsrcs = new ArrayList<>();
+            parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
+            parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
+            parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
+            parseResources(config, auxgroup + ".presource", Resource.PRELOAD, rsrcs);
+            parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
+            _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
+        }
+
+        // transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
+        String[] jvmargs = config.getMultiValue("jvmarg");
+        addAll(jvmargs, _jvmargs);
+        if (appPrefix.length() > 0) {
+            jvmargs = config.getMultiValue(appPrefix + "jvmarg");
+            addAll(jvmargs, _jvmargs);
+        }
+
+        // see if a percentage of physical memory option exists
+        int jvmmempc = config.getInt("jvmmempc", -1);
+        // app_id prefixed setting overrides
+        if (appPrefix.length() > 0) {
+            jvmmempc = config.getInt(appPrefix + "jvmmempc", jvmmempc);
+        }
+        if (0 <= jvmmempc && jvmmempc <= 100) {
+            final Object o = ManagementFactory.getOperatingSystemMXBean();
+
+            try {
+                if (o instanceof OperatingSystemMXBean) {
+                    final OperatingSystemMXBean osb = (OperatingSystemMXBean) o;
+                    long physicalMem = osb.getTotalPhysicalMemorySize();
+                    long requestedMem = physicalMem*jvmmempc/100;
+                    String[] maxMemHeapArg = new String[]{"-Xmx"+Long.toString(requestedMem)};
+                    // remove other max heap size arg
+                    ARG: for (int i = 0; i < _jvmargs.size(); i++) {
+                           if (_jvmargs.get(i) instanceof java.lang.String && _jvmargs.get(i).startsWith("-Xmx")) {
+                                _jvmargs.remove(i);
+                           }
+                    }
+                    addAll(maxMemHeapArg, _jvmargs);
+
+                }
+            }
+            catch (NoClassDefFoundError e) {
+                // com.sun.management.OperatingSystemMXBean doesn't exist in this JVM
+                System.out.println("No com.sun.management.OperatingSystemMXBean. Cannot use 'jvmmempc'.");
+            }
+        } else if (jvmmempc != -1) {
+          System.out.println("'jvmmempc' value must be in range 0 to 100 (read as '"+Integer.toString(jvmmempc)+"')");
+        }
+
+        // get the set of optimum JVM arguments
+        _optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
+
+        // transfer our application arguments
+        String[] appargs = config.getMultiValue(appPrefix + "apparg");
+        addAll(appargs, _appargs);
+
+        // add the launch specific application arguments
+        _appargs.addAll(_envc.appArgs);
+        
+        // look for custom arguments
+        fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
+
+        // determine whether we want to allow offline operation (defaults to false)
+        _allowOffline = config.getBoolean("allow_offline");
+
+        // look for a debug.txt file which causes us to run in java.exe on Windows so that we can
+        // obtain a thread dump of the running JVM
+        _windebug = getLocalPath("debug.txt").exists();
+
+        // whether to cache code resources and launch from cache
+        _useCodeCache = config.getBoolean("use_code_cache");
+        _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
+
+        // maximum simultaneous downloads
+        _maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads",
+                                                      SysProps.threadPoolSize()));
+
+        // extract some info used to configure our child process on macOS
+        _dockName = config.getString("ui.name");
+        _dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns");
+
+        return config;
+    }
+
+    /**
+     * Adds strings of the form pair0=pair1 to collector for each pair parsed out of pairLocation.
+     */
+    protected void fillAssignmentListFromPairs (String pairLocation, List<String> collector)
+    {
+        File pairFile = getLocalPath(pairLocation);
+        if (pairFile.exists()) {
+            try {
+                List<String[]> args = Config.parsePairs(pairFile, Config.createOpts(false));
+                for (String[] pair : args) {
+                    if (pair[1].length() == 0) {
+                        collector.add(pair[0]);
+                    } else {
+                        collector.add(pair[0] + "=" + pair[1]);
+                    }
+                }
+            } catch (Throwable t) {
+                log.warning("Failed to parse '" + pairFile + "': " + t);
+            }
+        }
+    }
+
+    /**
+     * Returns a URL from which the specified path can be fetched. Our application base URL is
+     * properly versioned and combined with the supplied path.
+     */
+    public URL getRemoteURL (String path)
+        throws MalformedURLException
+    {
+        return new URL(_vappbase, encodePath(path));
+    }
+
+    /**
+     * Returns the local path to the specified resource.
+     */
+    public File getLocalPath (String path)
+    {
+        return getLocalPath(getAppDir(), path);
+    }
+
+    /**
+     * Returns true if we either have no version requirement, are running in a JVM that meets our
+     * version requirements or have what appears to be a version of the JVM that meets our
+     * requirements.
+     */
+    public boolean haveValidJavaVersion ()
+    {
+        // if we're doing no version checking, then yay!
+        if (_javaMinVersion == 0 && _javaMaxVersion == 0) return true;
+
+        try {
+            // parse the version out of the java.version (or custom) system property
+            long version = SysProps.parseJavaVersion(_javaVersionProp, _javaVersionRegex);
+
+            log.info("Checking Java version", "current", version,
+                     "wantMin", _javaMinVersion, "wantMax", _javaMaxVersion);
+
+            // if we have an unpacked VM, check the 'release' file for its version
+            Resource vmjar = getJavaVMResource();
+            if (vmjar != null && vmjar.isMarkedValid()) {
+                File vmdir = new File(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR);
+                File relfile = new File(vmdir, "release");
+                if (!relfile.exists()) {
+                    log.warning("Unpacked JVM missing 'release' file. Assuming valid version.");
+                    return true;
+                }
+
+                long vmvers = VersionUtil.readReleaseVersion(relfile, _javaVersionRegex);
+                if (vmvers == 0L) {
+                    log.warning("Unable to read version from 'release' file. Assuming valid.");
+                    return true;
+                }
+
+                version = vmvers;
+                log.info("Checking version of unpacked JVM [vers=" + version + "].");
+            }
+
+            if (_javaExactVersionRequired) {
+                if (version == _javaMinVersion) return true;
+                else {
+                    log.warning("An exact Java VM version is required.", "current", version,
+                                "required", _javaMinVersion);
+                    return false;
+                }
+            }
+
+            boolean minVersionOK = (_javaMinVersion == 0) || (version >= _javaMinVersion);
+            boolean maxVersionOK = (_javaMaxVersion == 0) || (version <= _javaMaxVersion);
+            return minVersionOK && maxVersionOK;
+
+        } catch (RuntimeException re) {
+            // if we can't parse the java version we're in weird land and should probably just try
+            // our luck with what we've got rather than try to download a new jvm
+            log.warning("Unable to parse VM version, hoping for the best",
+                        "error", re, "needed", _javaMinVersion);
+            return true;
+        }
+    }
+
+    /**
+     * Checks whether the app has a set of "optimum" JVM args that we wish to try first, detecting
+     * whether the launch is successful and, if necessary, trying again without the optimum
+     * arguments.
+     */
+    public boolean hasOptimumJvmArgs ()
+    {
+        return _optimumJvmArgs != null;
+    }
+
+    /**
+     * Returns true if the app should attempt to run even if we have no Internet connection.
+     */
+    public boolean allowOffline ()
+    {
+        return _allowOffline;
+    }
+
+    /**
+     * Attempts to redownload the <code>getdown.txt</code> file based on information parsed from a
+     * previous call to {@link #init}.
+     */
+    public void attemptRecovery (StatusDisplay status)
+        throws IOException
+    {
+        status.updateStatus("m.updating_metadata");
+        downloadConfigFile();
+    }
+
+    /**
+     * Downloads and replaces the <code>getdown.txt</code> and <code>digest.txt</code> files with
+     * those for the target version of our application.
+     */
+    public void updateMetadata ()
+        throws IOException
+    {
+        try {
+            // update our versioned application base with the target version
+            _vappbase = createVAppBase(_targetVersion);
+        } catch (MalformedURLException mue) {
+            String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
+            throw (IOException) new IOException(err).initCause(mue);
+        }
+
+        try {
+            // now re-download our control files; we download the digest first so that if it fails,
+            // our config file will still reference the old version and re-running the updater will
+            // start the whole process over again
+            downloadDigestFiles();
+            downloadConfigFile();
+
+        } catch (IOException ex) {
+            // if we are allowing offline execution, we want to allow the application to run in its
+            // current form rather than aborting the entire process; to do this, we delete the
+            // version.txt file and "trick" Getdown into thinking that it just needs to validate
+            // the application as is; next time the app runs when connected to the internet, it
+            // will have to rediscover that it needs updating and reattempt to update itself
+            if (_allowOffline) {
+                log.warning("Failed to update digest files.  Attempting offline operaton.", ex);
+                if (!FileUtil.deleteHarder(getLocalPath(VERSION_FILE))) {
+                    log.warning("Deleting version.txt failed.  This probably isn't going to work.");
+                }
+            } else {
+                throw ex;
+            }
+        }
+    }
+
+    /**
+     * Invokes the process associated with this application definition.
+     *
+     * @param optimum whether or not to include the set of optimum arguments (as opposed to falling
+     * back).
+     */
+    public Process createProcess (boolean optimum)
+        throws IOException
+    {
+        ArrayList<String> args = new ArrayList<>();
+
+        // reconstruct the path to the JVM
+        args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum));
+
+        // check whether we're using -jar mode or -classpath mode
+        boolean dashJarMode = MANIFEST_CLASS.equals(_class);
+
+        // add the -classpath arguments if we're not in -jar mode
+        ClassPath classPath = PathBuilder.buildClassPath(this);
+        if (!dashJarMode) {
+            args.add("-classpath");
+            args.add(classPath.asArgumentString());
+        }
+
+        // we love our Mac users, so we do nice things to preserve our application identity
+        if (LaunchUtil.isMacOS()) {
+            args.add("-Xdock:icon=" + getLocalPath(_dockIconPath).getAbsolutePath());
+            args.add("-Xdock:name=" + _dockName);
+        }
+
+        // pass along our proxy settings
+        String proxyHost;
+        if ((proxyHost = System.getProperty("http.proxyHost")) != null) {
+            args.add("-Dhttp.proxyHost=" + proxyHost);
+            args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort"));
+            args.add("-Dhttps.proxyHost=" + proxyHost);
+            args.add("-Dhttps.proxyPort=" + System.getProperty("http.proxyPort"));
+        }
+
+        // add the marker indicating the app is running in getdown
+        args.add("-D" + Properties.GETDOWN + "=true");
+
+        // set the native library path if we have native resources
+        // @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false?
+        ClassPath javaLibPath = PathBuilder.buildLibsPath(this, true);
+        if (javaLibPath != null) {
+            args.add("-Djava.library.path=" + javaLibPath.asArgumentString());
+        }
+
+        // pass along any pass-through arguments
+        for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
+            String key = (String)entry.getKey();
+            if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) {
+                key = key.substring(PROP_PASSTHROUGH_PREFIX.length());
+                args.add("-D" + key + "=" + entry.getValue());
+            }
+        }
+
+        // add the JVM arguments
+        for (String string : _jvmargs) {
+            args.add(processArg(string));
+        }
+
+        // add the optimum arguments if requested and available
+        if (optimum && _optimumJvmArgs != null) {
+            for (String string : _optimumJvmArgs) {
+                args.add(processArg(string));
+            }
+        }
+
+        // add the arguments from extra.txt (after the optimum ones, in case they override them)
+        for (String string : _txtJvmArgs) {
+            args.add(processArg(string));
+        }
+
+        // if we're in -jar mode add those arguments, otherwise add the app class name
+        if (dashJarMode) {
+            args.add("-jar");
+            args.add(classPath.asArgumentString());
+        } else {
+            args.add(_class);
+        }
+
+        // almost finally check the startup file arguments
+        for (File f : _startupFiles) {
+          _appargs.add(f.getAbsolutePath());
+          break; // Only add one file to open
+        }
+        
+        // check if one arg with recognised extension
+        if ( _appargs.size() == 1 && _appargs.get(0) != null ) {
+          String filename = _appargs.get(0);
+          String ext = null;
+          int j = filename.lastIndexOf('.');
+          if (j > -1) {
+            ext = filename.substring(j+1);
+          }
+          if (LOCATOR_FILE_EXTENSION.equals(ext.toLowerCase())) {
+            // this file extension should have been dealt with in Getdown class
+          } else {
+            _appargs.add(0, "-open");
+          }
+        }
+
+        // finally add the application arguments
+        for (String string : _appargs) {
+            args.add(processArg(string));
+        }
+        
+        String[] envp = createEnvironment();
+        String[] sargs = args.toArray(new String[args.size()]);
+        log.info("Running " + StringUtil.join(sargs, "\n  "));
+
+        return Runtime.getRuntime().exec(sargs, envp, getAppDir());
+    }
+
+    /**
+     * If the application provided environment variables, combine those with the current
+     * environment and return that in a style usable for {@link Runtime#exec(String, String[])}.
+     * If the application didn't provide any environment variables, null is returned to just use
+     * the existing environment.
+     */
+    protected String[] createEnvironment ()
+    {
+        List<String> envvar = new ArrayList<>();
+        fillAssignmentListFromPairs("env.txt", envvar);
+        if (envvar.isEmpty()) {
+            log.info("Didn't find any custom environment variables, not setting any.");
+            return null;
+        }
+
+        List<String> envAssignments = new ArrayList<>();
+        for (String assignment : envvar) {
+            envAssignments.add(processArg(assignment));
+        }
+        for (Map.Entry<String, String> environmentEntry : System.getenv().entrySet()) {
+            envAssignments.add(environmentEntry.getKey() + "=" + environmentEntry.getValue());
+        }
+        String[] envp = envAssignments.toArray(new String[envAssignments.size()]);
+        log.info("Environment " + StringUtil.join(envp, "\n "));
+        return envp;
+    }
+
+    /**
+     * Runs this application directly in the current VM.
+     */
+    public void invokeDirect () throws IOException
+    {
+        ClassPath classPath = PathBuilder.buildClassPath(this);
+        URL[] jarUrls = classPath.asUrls();
+
+        // create custom class loader
+        URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) {
+            @Override protected PermissionCollection getPermissions (CodeSource code) {
+                Permissions perms = new Permissions();
+                perms.add(new AllPermission());
+                return perms;
+            }
+        };
+        Thread.currentThread().setContextClassLoader(loader);
+
+        log.info("Configured URL class loader:");
+        for (URL url : jarUrls) log.info("  " + url);
+
+        // configure any system properties that we can
+        for (String jvmarg : _jvmargs) {
+            if (jvmarg.startsWith("-D")) {
+                jvmarg = processArg(jvmarg.substring(2));
+                int eqidx = jvmarg.indexOf("=");
+                if (eqidx == -1) {
+                    log.warning("Bogus system property: '" + jvmarg + "'?");
+                } else {
+                    System.setProperty(jvmarg.substring(0, eqidx), jvmarg.substring(eqidx+1));
+                }
+            }
+        }
+
+        // pass along any pass-through arguments
+        Map<String, String> passProps = new HashMap<>();
+        for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
+            String key = (String)entry.getKey();
+            if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) {
+                key = key.substring(PROP_PASSTHROUGH_PREFIX.length());
+                passProps.put(key, (String)entry.getValue());
+            }
+        }
+        // we can't set these in the above loop lest we get a ConcurrentModificationException
+        for (Map.Entry<String, String> entry : passProps.entrySet()) {
+            System.setProperty(entry.getKey(), entry.getValue());
+        }
+
+        // prepare our app arguments
+        String[] args = new String[_appargs.size()];
+        for (int ii = 0; ii < args.length; ii++) args[ii] = processArg(_appargs.get(ii));
+
+        try {
+            log.info("Loading " + _class);
+            Class<?> appclass = loader.loadClass(_class);
+            Method main = appclass.getMethod("main", EMPTY_STRING_ARRAY.getClass());
+            log.info("Invoking main({" + StringUtil.join(args, ", ") + "})");
+            main.invoke(null, new Object[] { args });
+        } catch (Exception e) {
+            log.warning("Failure invoking app main", e);
+        }
+    }
+
+    /** Replaces the application directory and version in any argument. */
+    protected String processArg (String arg)
+    {
+        arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
+        arg = arg.replace("%VERSION%", String.valueOf(_version));
+
+        // if this argument contains %ENV.FOO% replace those with the associated values looked up
+        // from the environment
+        if (arg.contains(ENV_VAR_PREFIX)) {
+            StringBuffer sb = new StringBuffer();
+            Matcher matcher = ENV_VAR_PATTERN.matcher(arg);
+            while (matcher.find()) {
+                String varName = matcher.group(1), varValue = System.getenv(varName);
+                String repValue = varValue == null ? "MISSING:"+varName : varValue;
+                matcher.appendReplacement(sb, Matcher.quoteReplacement(repValue));
+            }
+            matcher.appendTail(sb);
+            arg = sb.toString();
+        }
+
+        return arg;
+    }
+
+    /**
+     * Loads the <code>digest.txt</code> file and verifies the contents of both that file and the
+     * <code>getdown.text</code> file. Then it loads the <code>version.txt</code> and decides
+     * whether or not the application needs to be updated or whether we can proceed to verification
+     * and execution.
+     *
+     * @return true if the application needs to be updated, false if it is up to date and can be
+     * verified and executed.
+     *
+     * @exception IOException thrown if we encounter an unrecoverable error while verifying the
+     * metadata.
+     */
+    public boolean verifyMetadata (StatusDisplay status)
+        throws IOException
+    {
+        log.info("Verifying application: " + _vappbase);
+        log.info("Version: " + _version);
+        log.info("Class: " + _class);
+
+        // this will read in the contents of the digest file and validate itself
+        try {
+            _digest = new Digest(getAppDir(), _strictComments);
+        } catch (IOException ioe) {
+            log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery...");
+        }
+
+        // if we have no version, then we are running in unversioned mode so we need to download
+        // our digest.txt file on every invocation
+        if (_version == -1) {
+            // make a note of the old meta-digest, if this changes we need to revalidate all of our
+            // resources as one or more of them have also changed
+            String olddig = (_digest == null) ? "" : _digest.getMetaDigest();
+            try {
+                status.updateStatus("m.checking");
+                downloadDigestFiles();
+                _digest = new Digest(getAppDir(), _strictComments);
+                if (!olddig.equals(_digest.getMetaDigest())) {
+                    log.info("Unversioned digest changed. Revalidating...");
+                    status.updateStatus("m.validating");
+                    clearValidationMarkers();
+                }
+            } catch (IOException ioe) {
+                log.warning("Failed to refresh non-versioned digest: " +
+                            ioe.getMessage() + ". Proceeding...");
+            }
+        }
+
+        // regardless of whether we're versioned, if we failed to read the digest from disk, try to
+        // redownload the digest file and give it another good college try; this time we allow
+        // exceptions to propagate up to the caller as there is nothing else we can do
+        if (_digest == null) {
+            status.updateStatus("m.updating_metadata");
+            downloadDigestFiles();
+            _digest = new Digest(getAppDir(), _strictComments);
+        }
+
+        // now verify the contents of our main config file
+        Resource crsrc = getConfigResource();
+        if (!_digest.validateResource(crsrc, null)) {
+            status.updateStatus("m.updating_metadata");
+            // attempt to redownload both of our metadata files; again we pass errors up to our
+            // caller because there's nothing we can do to automatically recover
+            downloadConfigFile();
+            downloadDigestFiles();
+            _digest = new Digest(getAppDir(), _strictComments);
+            // revalidate everything if we end up downloading new metadata
+            clearValidationMarkers();
+            // if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage
+            if (_digest.validateResource(crsrc, null)) {
+                init(true);
+            } else {
+                log.warning(CONFIG_FILE + " failed to validate even after redownloading. " +
+                            "Blindly forging onward.");
+            }
+        }
+
+        // start by assuming we are happy with our version
+        _targetVersion = _version;
+
+        // if we are a versioned application, read in the contents of the version.txt file
+        // and/or check the latest config URL for a newer version
+        if (_version != -1) {
+            File vfile = getLocalPath(VERSION_FILE);
+            long fileVersion = VersionUtil.readVersion(vfile);
+            if (fileVersion != -1) {
+                _targetVersion = fileVersion;
+            }
+
+            if (_latest != null) {
+                try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream();
+                     InputStreamReader reader = new InputStreamReader(in, UTF_8);
+                     BufferedReader bin = new BufferedReader(reader)) {
+                    for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
+                        if (pair[0].equals("version")) {
+                            _targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
+                            if (fileVersion != -1 && _targetVersion > fileVersion) {
+                                // replace the file with the newest version
+                                try (FileOutputStream fos = new FileOutputStream(vfile);
+                                     PrintStream out = new PrintStream(fos)) {
+                                    out.println(_targetVersion);
+                                }
+                            }
+                            break;
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warning("Unable to retrieve version from latest config file.", e);
+                }
+            }
+        }
+
+        // finally let the caller know if we need an update
+        return _version != _targetVersion;
+    }
+
+    /**
+     * Verifies the code and media resources associated with this application. A list of resources
+     * that do not exist or fail the verification process will be returned. If all resources are
+     * ready to go, null will be returned and the application is considered ready to run.
+     *
+     * @param obs a progress observer that will be notified of verification progress. NOTE: this
+     * observer may be called from arbitrary threads, so if you update a UI based on calls to it,
+     * you have to take care to get back to your UI thread.
+     * @param alreadyValid if non-null a 1 element array that will have the number of "already
+     * validated" resources filled in.
+     * @param unpacked a set to populate with unpacked resources.
+     * @param toInstall a list into which to add resources that need to be installed.
+     * @param toDownload a list into which to add resources that need to be downloaded.
+     */
+    public void verifyResources (
+        ProgressObserver obs, int[] alreadyValid, Set<Resource> unpacked,
+        Set<Resource> toInstall, Set<Resource> toDownload)
+        throws InterruptedException
+    {
+        // resources are verified on background threads supplied by the thread pool, and progress
+        // is reported by posting runnable actions to the actions queue which is processed by the
+        // main (UI) thread
+        ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize());
+        final BlockingQueue<Runnable> actions = new LinkedBlockingQueue<Runnable>();
+        final int[] completed = new int[1];
+
+        long start = System.currentTimeMillis();
+
+        // obtain the sizes of the resources to validate
+        List<Resource> rsrcs = getAllActiveResources();
+        long[] sizes = new long[rsrcs.size()];
+        long totalSize = 0;
+        for (int ii = 0; ii < sizes.length; ii++) {
+            totalSize += sizes[ii] = rsrcs.get(ii).getLocal().length();
+        }
+        final ProgressObserver fobs = obs;
+        // as long as we forward aggregated progress updates to the UI thread, having multiple
+        // threads update a progress aggregator is "mostly" thread-safe
+        final ProgressAggregator pagg = new ProgressAggregator(new ProgressObserver() {
+            public void progress (final int percent) {
+                actions.add(new Runnable() {
+                    public void run () {
+                        fobs.progress(percent);
+                    }
+                });
+            }
+        }, sizes);
+
+        final int[] fAlreadyValid = alreadyValid;
+        final Set<Resource> toInstallAsync = new ConcurrentSkipListSet<>(toInstall);
+        final Set<Resource> toDownloadAsync = new ConcurrentSkipListSet<>();
+        final Set<Resource> unpackedAsync = new ConcurrentSkipListSet<>();
+
+        for (int ii = 0; ii < sizes.length; ii++) {
+            final Resource rsrc = rsrcs.get(ii);
+            final int index = ii;
+            exec.execute(new Runnable() {
+                public void run () {
+                    verifyResource(rsrc, pagg.startElement(index), fAlreadyValid,
+                                   unpackedAsync, toInstallAsync, toDownloadAsync);
+                    actions.add(new Runnable() {
+                        public void run () {
+                            completed[0] += 1;
+                        }
+                    });
+                }
+            });
+        }
+
+        while (completed[0] < rsrcs.size()) {
+            // we should be getting progress completion updates WAY more often than one every
+            // minute, so if things freeze up for 60 seconds, abandon ship
+            Runnable action = actions.poll(60, TimeUnit.SECONDS);
+            action.run();
+        }
+
+        exec.shutdown();
+
+        toInstall.addAll(toInstallAsync);
+        toDownload.addAll(toDownloadAsync);
+        unpacked.addAll(unpackedAsync);
+
+        long complete = System.currentTimeMillis();
+        log.info("Verified resources", "count", rsrcs.size(), "size", (totalSize/1024) + "k",
+                 "duration", (complete-start) + "ms");
+    }
+
+    private void verifyResource (Resource rsrc, ProgressObserver obs, int[] alreadyValid,
+                                 Set<Resource> unpacked,
+                                 Set<Resource> toInstall, Set<Resource> toDownload) {
+        if (rsrc.isMarkedValid()) {
+            if (alreadyValid != null) {
+                alreadyValid[0]++;
+            }
+            obs.progress(100);
+            return;
+        }
+
+        try {
+            if (_digest.validateResource(rsrc, obs)) {
+                // if the resource has a _new file, add it to to-install list
+                if (rsrc.getLocalNew().exists()) {
+                    toInstall.add(rsrc);
+                    return;
+                }
+                rsrc.applyAttrs();
+                unpacked.add(rsrc);
+                rsrc.markAsValid();
+                return;
+            }
+
+        } catch (Exception e) {
+            log.info("Failure verifying resource. Requesting redownload...",
+                     "rsrc", rsrc, "error", e);
+
+        } finally {
+            obs.progress(100);
+        }
+        toDownload.add(rsrc);
+    }
+
+    /**
+     * Unpacks the resources that require it (we know that they're valid).
+     *
+     * @param unpacked a set of resources to skip because they're already unpacked.
+     */
+    public void unpackResources (ProgressObserver obs, Set<Resource> unpacked)
+        throws InterruptedException
+    {
+        List<Resource> rsrcs = getActiveResources();
+
+        // remove resources that we don't want to unpack
+        for (Iterator<Resource> it = rsrcs.iterator(); it.hasNext(); ) {
+            Resource rsrc = it.next();
+            if (!rsrc.shouldUnpack() || unpacked.contains(rsrc)) {
+                it.remove();
+            }
+        }
+
+        // obtain the sizes of the resources to unpack
+        long[] sizes = new long[rsrcs.size()];
+        for (int ii = 0; ii < sizes.length; ii++) {
+            sizes[ii] = rsrcs.get(ii).getLocal().length();
+        }
+
+        ProgressAggregator pagg = new ProgressAggregator(obs, sizes);
+        for (int ii = 0; ii < sizes.length; ii++) {
+            Resource rsrc = rsrcs.get(ii);
+            ProgressObserver pobs = pagg.startElement(ii);
+            try {
+                rsrc.unpack();
+            } catch (IOException ioe) {
+                log.warning("Failure unpacking resource", "rsrc", rsrc, ioe);
+            }
+            pobs.progress(100);
+        }
+    }
+
+    /**
+     * Clears all validation marker files.
+     */
+    public void clearValidationMarkers ()
+    {
+        clearValidationMarkers(getAllActiveResources().iterator());
+    }
+
+    /**
+     * Returns the version number for the application.  Should only be called after successful
+     * return of verifyMetadata.
+     */
+    public long getVersion ()
+    {
+        return _version;
+    }
+
+    /**
+     * Creates a versioned application base URL for the specified version.
+     */
+    protected URL createVAppBase (long version)
+        throws MalformedURLException
+    {
+        String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version);
+        return HostWhitelist.verify(new URL(url));
+    }
+
+    /**
+     * Clears all validation marker files for the resources in the supplied iterator.
+     */
+    protected void clearValidationMarkers (Iterator<Resource> iter)
+    {
+        while (iter.hasNext()) {
+            iter.next().clearMarker();
+        }
+    }
+
+    /**
+     * Downloads a new copy of CONFIG_FILE.
+     */
+    protected void downloadConfigFile ()
+        throws IOException
+    {
+        downloadControlFile(CONFIG_FILE, 0);
+    }
+
+    /**
+     * @return true if gettingdown.lock was unlocked, already locked by this application or if
+     * we're not locking at all.
+     */
+    public synchronized boolean lockForUpdates ()
+    {
+        if (_lock != null && _lock.isValid()) {
+            return true;
+        }
+        try {
+            _lockChannel = new RandomAccessFile(getLocalPath("gettingdown.lock"), "rw").getChannel();
+        } catch (FileNotFoundException e) {
+            log.warning("Unable to create lock file", "message", e.getMessage(), e);
+            return false;
+        }
+        try {
+            _lock = _lockChannel.tryLock();
+        } catch (IOException e) {
+            log.warning("Unable to create lock", "message", e.getMessage(), e);
+            return false;
+        } catch (OverlappingFileLockException e) {
+            log.warning("The lock is held elsewhere in this JVM", e);
+            return false;
+        }
+        log.info("Able to lock for updates: " + (_lock != null));
+        return _lock != null;
+    }
+
+    /**
+     * Release gettingdown.lock
+     */
+    public synchronized void releaseLock ()
+    {
+        if (_lock != null) {
+            log.info("Releasing lock");
+            try {
+                _lock.release();
+            } catch (IOException e) {
+                log.warning("Unable to release lock", "message", e.getMessage(), e);
+            }
+            try {
+                _lockChannel.close();
+            } catch (IOException e) {
+                log.warning("Unable to close lock channel", "message", e.getMessage(), e);
+            }
+            _lockChannel = null;
+            _lock = null;
+        }
+    }
+
+    /**
+     * Downloads the digest files and validates their signature.
+     * @throws IOException
+     */
+    protected void downloadDigestFiles ()
+        throws IOException
+    {
+        for (int version = 1; version <= Digest.VERSION; version++) {
+            downloadControlFile(Digest.digestFile(version), version);
+        }
+    }
+
+    /**
+     * Downloads a new copy of the specified control file, optionally validating its signature.
+     * If the download is successful, moves it over the old file on the filesystem.
+     *
+     * <p> TODO: Switch to PKCS #7 or CMS.
+     *
+     * @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this
+     * should indicate the version of the digest file being validated which indicates which
+     * algorithm to use to verify the signature. See {@link Digest#VERSION}.
+     */
+    protected void downloadControlFile (String path, int sigVersion)
+        throws IOException
+    {
+        File target = downloadFile(path);
+
+        if (sigVersion > 0) {
+            if (_envc.certs.isEmpty()) {
+                log.info("No signing certs, not verifying digest.txt", "path", path);
+
+            } else {
+                File signatureFile = downloadFile(path + SIGNATURE_SUFFIX);
+                byte[] signature = null;
+                try (FileInputStream signatureStream = new FileInputStream(signatureFile)) {
+                    signature = StreamUtil.toByteArray(signatureStream);
+                } finally {
+                    FileUtil.deleteHarder(signatureFile); // delete the file regardless
+                }
+
+                byte[] buffer = new byte[8192];
+                int length, validated = 0;
+                for (Certificate cert : _envc.certs) {
+                    try (FileInputStream dataInput = new FileInputStream(target)) {
+                        Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion));
+                        sig.initVerify(cert);
+                        while ((length = dataInput.read(buffer)) != -1) {
+                            sig.update(buffer, 0, length);
+                        }
+
+                        if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
+                            log.info("Signature does not match", "cert", cert.getPublicKey());
+                            continue;
+                        } else {
+                            log.info("Signature matches", "cert", cert.getPublicKey());
+                            validated++;
+                        }
+
+                    } catch (IOException ioe) {
+                        log.warning("Failure validating signature of " + target + ": " + ioe);
+
+                    } catch (GeneralSecurityException gse) {
+                        // no problem!
+
+                    }
+                }
+
+                // if we couldn't find a key that validates our digest, we are the hosed!
+                if (validated == 0) {
+                    // delete the temporary digest file as we know it is invalid
+                    FileUtil.deleteHarder(target);
+                    throw new IOException("m.corrupt_digest_signature_error");
+                }
+            }
+        }
+
+        // now move the temporary file over the original
+        File original = getLocalPath(path);
+        if (!FileUtil.renameTo(target, original)) {
+            throw new IOException("Failed to rename(" + target + ", " + original + ")");
+        }
+    }
+
+    /**
+     * Download a path to a temporary file, returning a {@link File} instance with the path
+     * contents.
+     */
+    protected File downloadFile (String path)
+        throws IOException
+    {
+        File target = getLocalPath(path + "_new");
+
+        URL targetURL = null;
+        try {
+            targetURL = getRemoteURL(path);
+        } catch (Exception e) {
+            log.warning("Requested to download invalid control file",
+                "appbase", _vappbase, "path", path, "error", e);
+            throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e);
+        }
+
+        log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
+
+        // stream the URL into our temporary file
+        URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0);
+        // we have to tell Java not to use caches here, otherwise it will cache any request for
+        // same URL for the lifetime of this JVM (based on the URL string, not the URL object);
+        // if the getdown.txt file, for example, changes in the meanwhile, we would never hear
+        // about it; turning off caches is not a performance concern, because when Getdown asks
+        // to download a file, it expects it to come over the wire, not from a cache
+        uconn.setUseCaches(false);
+        uconn.setRequestProperty("Accept-Encoding", "gzip");
+        try (InputStream fin = uconn.getInputStream()) {
+            String encoding = uconn.getContentEncoding();
+            boolean gzip = "gzip".equalsIgnoreCase(encoding);
+            try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
+                try (FileOutputStream fout = new FileOutputStream(target)) {
+                    StreamUtil.copy(fin2, fout);
+                }
+            }
+        }
+
+        return target;
+    }
+
+    /** Helper function for creating {@link Resource} instances. */
+    protected Resource createResource (String path, EnumSet<Resource.Attr> attrs)
+        throws MalformedURLException
+    {
+        return new Resource(path, getRemoteURL(path), getLocalPath(path), attrs);
+    }
+
+    /** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
+    protected static void addAll (String[] values, List<String> target) {
+        if (values != null) {
+            for (String value : values) {
+                target.add(value);
+            }
+        }
+    }
+
+    /**
+     * Make an immutable List from the specified int array.
+     */
+    public static List<Integer> intsToList (int[] values)
+    {
+        List<Integer> list = new ArrayList<>(values.length);
+        for (int val : values) {
+            list.add(val);
+        }
+        return Collections.unmodifiableList(list);
+    }
+
+    /**
+     * Make an immutable List from the specified String array.
+     */
+    public static List<String> stringsToList (String[] values)
+    {
+        return values == null ? null : Collections.unmodifiableList(Arrays.asList(values));
+    }
+
+    /** Used to parse resources with the specified name. */
+    protected void parseResources (Config config, String name, EnumSet<Resource.Attr> attrs,
+                                   List<Resource> list)
+    {
+        String[] rsrcs = config.getMultiValue(name);
+        if (rsrcs == null) {
+            return;
+        }
+        for (String rsrc : rsrcs) {
+            try {
+                list.add(createResource(rsrc, attrs));
+            } catch (Exception e) {
+                log.warning("Invalid resource '" + rsrc + "'. " + e);
+            }
+        }
+    }
+
+    /** Possibly generates and returns a google analytics tracking cookie. */
+    protected String getGATrackingCode ()
+    {
+        if (_trackingGAHash == null) {
+            return "";
+        }
+        long time = System.currentTimeMillis() / 1000;
+        if (_trackingStart == 0) {
+            _trackingStart = time;
+        }
+        if (_trackingId == 0) {
+            int low = 100000000, high = 1000000000;
+            _trackingId = low + _rando.nextInt(high-low);
+        }
+        StringBuilder cookie = new StringBuilder("&utmcc=__utma%3D").append(_trackingGAHash);
+        cookie.append(".").append(_trackingId);
+        cookie.append(".").append(_trackingStart).append(".").append(_trackingStart);
+        cookie.append(".").append(time).append(".1%3B%2B");
+        cookie.append("__utmz%3D").append(_trackingGAHash).append(".");
+        cookie.append(_trackingStart).append(".1.1.");
+        cookie.append("utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B");
+        int low = 1000000000, high = 2000000000;
+        cookie.append("&utmn=").append(_rando.nextInt(high-low));
+        return cookie.toString();
+    }
+
+    /**
+     * Encodes a path for use in a URL.
+     */
+    protected static String encodePath (String path)
+    {
+        try {
+            // we want to keep slashes because we're encoding an entire path; also we need to turn
+            // + into %20 because web servers don't like + in paths or file names, blah
+            return URLEncoder.encode(path, "UTF-8").replace("%2F", "/").replace("+", "%20");
+        } catch (UnsupportedEncodingException ue) {
+            log.warning("Failed to URL encode " + path + ": " + ue);
+            return path;
+        }
+    }
+
+    protected File getLocalPath (File appdir, String path)
+    {
+        return new File(appdir, path);
+    }
+
+    public static void setStartupFilesFromParameterString(String p) {
+      // multiple files *might* be passed in as space separated quoted filenames
+      String q = "\"";
+      if (!StringUtil.isBlank(p)) {
+        String[] filenames;
+        // split quoted params or treat as single string array
+        if (p.startsWith(q) && p.endsWith(q)) {
+          // this fails if, e.g.
+          // p=q("stupidfilename\" " "otherfilename")
+          // let's hope no-one ever ends a filename with '" '
+          filenames = p.substring(q.length(),p.length()-q.length()).split(q+" "+q);
+        } else {
+          // single unquoted filename
+          filenames = new String[]{p};
+        }
+
+        // check for locator file.  Only allow one locator file to be double clicked (if multiple files opened, ignore locator files)
+        String locatorFilename = filenames.length >= 1 ? filenames[0] : null;
+        if (
+                !StringUtil.isBlank(locatorFilename)
+                && locatorFilename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)
+                ) {
+          setLocatorFile(locatorFilename);
+          // remove the locator filename from the filenames array
+          String[] otherFilenames = new String[filenames.length - 1];
+          System.arraycopy(filenames, 1, otherFilenames, 0, otherFilenames.length);
+          filenames = otherFilenames;
+        }
+
+        for (int i = 0; i < filenames.length; i++) {
+          String filename = filenames[i];
+          // skip any other locator files in a multiple file list
+          if (! filename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)) {
+            addStartupFile(filename);
+          }
+        }
+      }
+    }
+    
+    public static void setLocatorFile(String filename) {
+      _locatorFile = new File(filename);
+    }
+    
+    public static void addStartupFile(String filename) {
+      _startupFiles.add(new File(filename));
+    }
+    
+    private Config createLocatorConfig(Config.ParseOpts opts) {
+      if (_locatorFile == null) {
+        return null;
+      }
+      
+      Config locatorConfig = null;
+      
+      try {
+        Config tmpConfig = null;
+        if (_locatorFile.exists()) {
+          tmpConfig = Config.parseConfig(_locatorFile,  opts);
+        } else {
+          log.warning("Given locator file does not exist", "file", _locatorFile);
+        }
+        
+        // appbase is sanitised in HostWhitelist
+        Map<String, Object> tmpData = new HashMap<>();
+        for (Map.Entry<String, Object> entry : tmpConfig.getData().entrySet()) {
+          String key = entry.getKey();
+          Object value = entry.getValue();
+          String mkey = key.indexOf('.') > -1 ? key.substring(key.indexOf('.') + 1) : key;
+          if (Config.allowedReplaceKeys.contains(mkey) || Config.allowedMergeKeys.contains(mkey)) {
+            tmpData.put(key, value);
+          }
+        }
+        locatorConfig = new Config(tmpData);
+        
+      } catch (Exception e) {
+        log.warning("Failure reading locator file",  "file", _locatorFile, e);
+      }
+      
+      log.info("Returning locatorConfig", locatorConfig);
+      
+      return locatorConfig;
+    }
+    
+    protected final EnvConfig _envc;
+    protected File _config;
+    protected Digest _digest;
+
+    protected long _version = -1;
+    protected long _targetVersion = -1;
+    protected String _appbase;
+    protected URL _vappbase;
+    protected URL _latest;
+    protected String _class;
+    protected String _dockName;
+    protected String _dockIconPath;
+    protected boolean _strictComments;
+    protected boolean _windebug;
+    protected boolean _allowOffline;
+    protected int _maxConcDownloads;
+
+    protected String _trackingURL;
+    protected Set<Integer> _trackingPcts;
+    protected String _trackingCookieName;
+    protected String _trackingCookieProperty;
+    protected String _trackingURLSuffix;
+    protected String _trackingGAHash;
+    protected long _trackingStart;
+    protected int _trackingId;
+
+    protected String _javaVersionProp = "java.version";
+    protected String _javaVersionRegex = "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?";
+    protected long _javaMinVersion, _javaMaxVersion;
+    protected boolean _javaExactVersionRequired;
+    protected String _javaLocation;
+
+    protected List<Resource> _codes = new ArrayList<>();
+    protected List<Resource> _resources = new ArrayList<>();
+
+    protected boolean _useCodeCache;
+    protected int _codeCacheRetentionDays;
+
+    protected Map<String,AuxGroup> _auxgroups = new HashMap<>();
+    protected Map<String,Boolean> _auxactive = new HashMap<>();
+
+    protected List<String> _jvmargs = new ArrayList<>();
+    protected List<String> _appargs = new ArrayList<>();
+
+    protected String[] _optimumJvmArgs;
+
+    protected List<String> _txtJvmArgs = new ArrayList<>();
+
+    /** If a warning has been issued about not being able to set modtimes. */
+    protected boolean _warnedAboutSetLastModified;
+
+    /** Locks gettingdown.lock in the app dir. Held the entire time updating is going on.*/
+    protected FileLock _lock;
+
+    /** Channel to the file underlying _lock.  Kept around solely so the lock doesn't close. */
+    protected FileChannel _lockChannel;
+
+    protected Random _rando = new Random();
+
+    protected static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    protected static final String ENV_VAR_PREFIX = "%ENV.";
+    protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%");
+    protected static File _locatorFile;
+    protected static List<File> _startupFiles = new ArrayList<>();
+    public static final String LOCATOR_FILE_EXTENSION = "jvl";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl
new file mode 100644 (file)
index 0000000..60a8ff3
--- /dev/null
@@ -0,0 +1,43 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2016 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.threerings.getdown.util.StringUtil;
+
+/**
+ * Contains static data provided during the build process.
+ */
+public class Build {
+
+    /** The date and time at which the code was built: in {@code yyyy-MM-dd HH:mm} format. */
+    public static String time () {
+        return "@build_time@";
+    }
+
+    /** The Maven version of the Getdown project. */
+    public static String version () {
+        return "@build_version@";
+    }
+
+    /**
+     * <p>The hosts which Getdown is allowed to communicate with. An empty list indicates that
+     * no whitelist is configured and there are no limitations. By default, no host whitelist
+     * is added to the binary, so it can be used to download and run applications from any
+     * server.
+     *
+     * <p>To create a custom Getdown build that can only talk to whitelisted servers, set
+     * the {@code getdown.host.whitelist} property on the command line while building the JAR
+     * (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used
+     * (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas
+     * (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}).
+     */
+    public static List<String> hostWhitelist () {
+        return Arrays.asList(StringUtil.parseStringArray("@host_whitelist@"));
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java
new file mode 100644 (file)
index 0000000..9c2fce3
--- /dev/null
@@ -0,0 +1,76 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Represents the class path and it's elements of the application to be launched. The class path
+ * can either be represented as an {@link #asArgumentString() argument string} for the java command
+ * line or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}.
+ */
+public class ClassPath
+{
+    public ClassPath (LinkedHashSet<File> classPathEntries)
+    {
+        _classPathEntries = Collections.unmodifiableSet(classPathEntries);
+    }
+
+    /**
+     * Returns the class path as an java command line argument string, e.g.
+     *
+     * <pre>
+     *   /path/to/a.jar:/path/to/b.jar
+     * </pre>
+     */
+    public String asArgumentString ()
+    {
+        StringBuilder builder = new StringBuilder();
+        String delimiter = "";
+        for (File entry: _classPathEntries) {
+            builder.append(delimiter).append(entry.getAbsolutePath());
+            delimiter = File.pathSeparator;
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Returns the class path entries as an array of URLs to be used for example by an
+     * {@link URLClassLoader}.
+     */
+    public URL[] asUrls ()
+    {
+        URL[] urls = new URL[_classPathEntries.size()];
+        int i = 0;
+        for (File entry : _classPathEntries) {
+            urls[i++] = getURL(entry);
+        }
+        return urls;
+    }
+
+    public Set<File> getClassPathEntries ()
+    {
+        return _classPathEntries;
+    }
+
+
+    private static URL getURL (File file)
+    {
+        try {
+            return file.toURI().toURL();
+        } catch (MalformedURLException e) {
+            throw new IllegalStateException("URL of file is illegal: " + file.getAbsolutePath(), e);
+        }
+    }
+
+    private final Set<File> _classPathEntries;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java
new file mode 100644 (file)
index 0000000..bc8d140
--- /dev/null
@@ -0,0 +1,228 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.concurrent.*;
+
+import com.threerings.getdown.util.Config;
+import com.threerings.getdown.util.MessageUtil;
+import com.threerings.getdown.util.ProgressObserver;
+import com.threerings.getdown.util.StringUtil;
+
+import static com.threerings.getdown.Log.log;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Manages the <code>digest.txt</code> file and the computing and processing of digests for an
+ * application.
+ */
+public class Digest
+{
+    /** The current version of the digest protocol. */
+    public static final int VERSION = 2;
+
+    /**
+     * Returns the name of the digest file for the specified protocol version.
+     */
+    public static String digestFile (int version) {
+        String infix = version > 1 ? String.valueOf(version) : "";
+        return FILE_NAME + infix + FILE_SUFFIX;
+    }
+
+    /**
+     * Returns the crypto algorithm used to sign digest files of the specified version.
+     */
+    public static String sigAlgorithm (int version) {
+        switch (version) {
+        case 1: return "SHA1withRSA";
+        case 2: return "SHA256withRSA";
+        default: throw new IllegalArgumentException("Invalid digest version " + version);
+        }
+    }
+
+    /**
+     * Creates a digest file at the specified location using the supplied list of resources.
+     * @param version the version of the digest protocol to use.
+     */
+    public static void createDigest (int version, List<Resource> resources, File output)
+        throws IOException
+    {
+        // first compute the digests for all the resources in parallel
+        ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize());
+        final Map<Resource, String> digests = new ConcurrentHashMap<>();
+        final BlockingQueue<Object> completed = new LinkedBlockingQueue<>();
+        final int fversion = version;
+
+        long start = System.currentTimeMillis();
+
+        Set<Resource> pending = new HashSet<>(resources);
+        for (final Resource rsrc : resources) {
+            exec.execute(new Runnable() {
+                public void run () {
+                    try {
+                        MessageDigest md = getMessageDigest(fversion);
+                        digests.put(rsrc, rsrc.computeDigest(fversion, md, null));
+                        completed.add(rsrc);
+                    } catch (Throwable t) {
+                        completed.add(new IOException("Error computing digest for: " + rsrc).
+                                      initCause(t));
+                    }
+                }
+            });
+        }
+
+        // queue a shutdown of the thread pool when the tasks are done
+        exec.shutdown();
+
+        try {
+            while (pending.size() > 0) {
+                Object done = completed.poll(600, TimeUnit.SECONDS);
+                if (done instanceof IOException) {
+                    throw (IOException)done;
+                } else if (done instanceof Resource) {
+                    pending.remove((Resource)done);
+                } else {
+                    throw new AssertionError("What is this? " + done);
+                }
+            }
+        } catch (InterruptedException ie) {
+            throw new IOException("Timeout computing digests. Wow.");
+        }
+
+        StringBuilder data = new StringBuilder();
+        try (FileOutputStream fos = new FileOutputStream(output);
+             OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
+             PrintWriter pout = new PrintWriter(osw)) {
+            // compute and append the digest of each resource in the list
+            for (Resource rsrc : resources) {
+                String path = rsrc.getPath();
+                String digest = digests.get(rsrc);
+                note(data, path, digest);
+                pout.println(path + " = " + digest);
+            }
+            // finally compute and append the digest for the file contents
+            MessageDigest md = getMessageDigest(version);
+            byte[] contents = data.toString().getBytes(UTF_8);
+            String filename = digestFile(version);
+            pout.println(filename + " = " + StringUtil.hexlate(md.digest(contents)));
+        }
+
+        long elapsed = System.currentTimeMillis() - start;
+        log.debug("Computed digests [rsrcs=" + resources.size() + ", time=" + elapsed + "ms]");
+    }
+
+    /**
+     * Obtains an appropriate message digest instance for use by the Getdown system.
+     */
+    public static MessageDigest getMessageDigest (int version)
+    {
+        String algo = version > 1 ? "SHA-256" : "MD5";
+        try {
+            return MessageDigest.getInstance(algo);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new RuntimeException("JVM does not support " + algo + ". Gurp!");
+        }
+    }
+
+    /**
+     * Creates a digest instance which will parse and validate the digest in the supplied
+     * application directory, using the current digest version.
+     */
+    public Digest (File appdir, boolean strictComments) throws IOException {
+        this(appdir, VERSION, strictComments);
+    }
+
+    /**
+     * Creates a digest instance which will parse and validate the digest in the supplied
+     * application directory.
+     * @param version the version of the digest protocol to use.
+     */
+    public Digest (File appdir, int version, boolean strictComments) throws IOException
+    {
+        // parse and validate our digest file contents
+        String filename = digestFile(version);
+        StringBuilder data = new StringBuilder();
+        File dfile = new File(appdir, filename);
+        Config.ParseOpts opts = Config.createOpts(false);
+        opts.strictComments = strictComments;
+        // bias = toward key: the key is the filename and could conceivably contain = signs, value
+        // is the hex encoded hash which will not contain =
+        opts.biasToKey = true;
+        for (String[] pair : Config.parsePairs(dfile, opts)) {
+            if (pair[0].equals(filename)) {
+                _metaDigest = pair[1];
+                break;
+            }
+            _digests.put(pair[0], pair[1]);
+            note(data, pair[0], pair[1]);
+        }
+
+        // we've reached the end, validate our contents
+        MessageDigest md = getMessageDigest(version);
+        byte[] contents = data.toString().getBytes(UTF_8);
+        String hash = StringUtil.hexlate(md.digest(contents));
+        if (!hash.equals(_metaDigest)) {
+            String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, hash);
+            throw new IOException(err);
+        }
+    }
+
+    /**
+     * Returns the digest for the digest file.
+     */
+    public String getMetaDigest ()
+    {
+        return _metaDigest;
+    }
+
+    /**
+     * Computes the hash of the specified resource and compares it with the value parsed from
+     * the digest file. Logs a message if the resource fails validation.
+     *
+     * @return true if the resource is valid, false if it failed the digest check or if an I/O
+     * error was encountered during the validation process.
+     */
+    public boolean validateResource (Resource resource, ProgressObserver obs)
+    {
+        try {
+            String chash = resource.computeDigest(VERSION, getMessageDigest(VERSION), obs);
+            String ehash = _digests.get(resource.getPath());
+            if (chash.equals(ehash)) {
+                return true;
+            }
+            log.info("Resource failed digest check",
+                     "rsrc", resource, "computed", chash, "expected", ehash);
+        } catch (Throwable t) {
+            log.info("Resource failed digest check", "rsrc", resource, "error", t);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the digest of the given {@code resource}.
+     */
+    public String getDigest (Resource resource)
+    {
+        return _digests.get(resource.getPath());
+    }
+
+    /** Used by {@link #createDigest} and {@link Digest}. */
+    protected static void note (StringBuilder data, String path, String digest)
+    {
+        data.append(path).append(" = ").append(digest).append("\n");
+    }
+
+    protected HashMap<String, String> _digests = new HashMap<>();
+    protected String _metaDigest = "";
+
+    protected static final String FILE_NAME = "digest";
+    protected static final String FILE_SUFFIX = ".txt";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
new file mode 100644 (file)
index 0000000..57b8d84
--- /dev/null
@@ -0,0 +1,245 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+import com.threerings.getdown.util.StringUtil;
+import com.threerings.getdown.data.Application;
+
+/** Configuration that comes from our "environment" (command line args, sys props, etc.). */
+public final class EnvConfig {
+
+    /** Used to report problems or feedback by {@link #create}. */
+    public static final class Note {
+        public static enum Level { INFO, WARN, ERROR };
+        public static Note info (String msg) { return new Note(Level.INFO, msg); }
+        public static Note warn (String msg) { return new Note(Level.WARN, msg); }
+        public static Note error (String msg) { return new Note(Level.ERROR, msg); }
+        public final Level level;
+        public final String message;
+        public Note (Level level, String message) {
+            this.level = level;
+            this.message = message;
+        }
+    }
+
+    /**
+     * Creates an environment config, obtaining information (in order) from the following sources:
+     *
+     * <ul>
+     * <li> A {@code bootstrap.properties} file bundled with the jar. </li>
+     * <li> System properties supplied to the JVM. </li>
+     * <li> The supplied command line arguments ({@code argv}). </li>
+     * </ul>
+     *
+     * If a later source supplies a configuration already provided by a prior source, a warning
+     * message will be logged to indicate the conflict, and the prior source will be used.
+     *
+     * @param notes a list into which notes are added, to be logged after the logging system has
+     * been initialized (which cannot happen until the appdir is known). If any {@code ERROR} notes
+     * are included, the app should terminate after reporting them.
+     * @return an env config instance, or {@code null} if no appdir could be located via any
+     * configuration source.
+     */
+    public static EnvConfig create (String[] argv, List<Note> notes) {
+        String appDir = null, appDirProv = null;
+        String appId = null, appIdProv = null;
+        String appBase = null, appBaseProv = null;
+
+        // start with bootstrap.properties config, if avaialble
+        try {
+            ResourceBundle bundle = ResourceBundle.getBundle("bootstrap");
+            if (bundle.containsKey("appdir")) {
+                appDir = bundle.getString("appdir");
+                appDir = appDir.replace(USER_HOME_KEY, System.getProperty("user.home"));
+                appDirProv = "bootstrap.properties";
+            }
+            if (bundle.containsKey("appid")) {
+                appId = bundle.getString("appid");
+                appIdProv = "bootstrap.properties";
+            }
+            if (bundle.containsKey("appbase")) {
+                appBase = bundle.getString("appbase");
+                appBaseProv = "bootstrap.properties";
+            }
+            // if any system properties are specified (keys prefixed with sys.), set those up
+            for (String key : bundle.keySet()) {
+                if (key.startsWith("sys.")) {
+                    String skey = key.substring(4);
+                    String svalue = bundle.getString(key);
+                    notes.add(Note.info("Setting system property from bundle: " +
+                                        skey + "='" + svalue + "'"));
+                    System.setProperty(skey, svalue);
+                }
+            }
+
+        } catch (MissingResourceException e) {
+            // bootstrap.properties is optional; no need for a warning
+        }
+
+        // next seek config from system properties
+        String spropsAppDir = SysProps.appDir();
+        if (!StringUtil.isBlank(spropsAppDir)) {
+            if (appDir == null) {
+                appDir = spropsAppDir;
+                appDirProv = "system property";
+            } else {
+                notes.add(Note.warn("Ignoring 'appdir' system property, have appdir via '" +
+                                    appDirProv + "'"));
+            }
+        }
+        String spropsAppId = SysProps.appId();
+        if (!StringUtil.isBlank(spropsAppId)) {
+            if (appId == null) {
+                appId = spropsAppId;
+                appIdProv = "system property";
+            } else {
+                notes.add(Note.warn("Ignoring 'appid' system property, have appid via '" +
+                                    appIdProv + "'"));
+            }
+        }
+        String spropsAppBase = SysProps.appBase();
+        if (!StringUtil.isBlank(spropsAppBase)) {
+            if (appBase == null) {
+                appBase = spropsAppBase;
+                appBaseProv = "system property";
+            } else {
+                notes.add(Note.warn("Ignoring 'appbase' system property, have appbase via '" +
+                                    appBaseProv + "'"));
+            }
+        }
+
+        // finally obtain config from command line arguments
+        String argvAppDir = argv.length > 0 ? argv[0] : null;
+        if (!StringUtil.isBlank(argvAppDir)) {
+            if (appDir == null) {
+                appDir = argvAppDir;
+                appDirProv = "command line";
+            } else {
+                notes.add(Note.warn("Ignoring 'appdir' command line arg, have appdir via '" +
+                                    appDirProv + "'"));
+            }
+        }
+        String argvAppId = argv.length > 1 ? argv[1] : null;
+        if (!StringUtil.isBlank(argvAppId)) {
+            if (appId == null) {
+                appId = argvAppId;
+                appIdProv = "command line";
+            } else {
+                notes.add(Note.warn("Ignoring 'appid' command line arg, have appid via '" +
+                                    appIdProv + "'"));
+            }
+        }
+        
+        int skipArgs = 2;
+        // Look for locator file, pass to Application and remove from appArgs
+        String argvLocatorFilename = argv.length > 2 ? argv[2] : null;
+        if (
+                !StringUtil.isBlank(argvLocatorFilename)
+                && argvLocatorFilename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)
+                ) {
+          notes.add(Note.info("locatorFilename in args: '"+argv[2]+"'"));
+          Application.setLocatorFile(argvLocatorFilename);
+          
+          skipArgs++;
+        }
+
+        // ensure that we were able to find an app dir
+        if (appDir == null) {
+            return null; // caller will report problem to user
+        }
+
+        notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir));
+        if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId));
+        if (appBase != null) notes.add(
+            Note.info("Using appbase from " + appBaseProv + ": " + appBase));
+
+        // ensure that the appdir refers to a directory that exists
+        File appDirFile = new File(appDir);
+        if (!appDirFile.exists()) {
+            // if we have a bootstrap URL then we auto-create the app dir; this enables an
+            // installer to simply place a getdown.jar file somewhere and create an OS shortcut
+            // that runs getdown with an appdir and appbase specified, and have getdown create the
+            // appdir and download the app into it
+            if (!StringUtil.isBlank(appBase)) {
+                if (appDirFile.mkdirs()) {
+                    notes.add(Note.info("Auto-created app directory '" + appDir + "'"));
+                } else {
+                    notes.add(Note.warn("Unable to auto-create app dir: '" + appDir + "'"));
+                }
+            } else {
+                notes.add(Note.error("Invalid appdir '" + appDir + "': directory does not exist"));
+                return null;
+            }
+        } else if (!appDirFile.isDirectory()) {
+            notes.add(Note.error("Invalid appdir '" + appDir + "': refers to non-directory"));
+            return null;
+        }
+
+        // pass along anything after the first two (or three) args as extra app args
+        List<String> appArgs = argv.length > skipArgs ?
+            Arrays.asList(argv).subList(skipArgs, argv.length) :
+            Collections.<String>emptyList();
+
+        // load X.509 certificate if it exists
+        File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt");
+        List<Certificate> certs = new ArrayList<>();
+        if (crtFile.exists()) {
+            try (FileInputStream fis = new FileInputStream(crtFile)) {
+                X509Certificate certificate = (X509Certificate)
+                    CertificateFactory.getInstance("X.509").generateCertificate(fis);
+                certs.add(certificate);
+            } catch (Exception e) {
+                notes.add(Note.error("Certificate error: " + e.getMessage()));
+            }
+        }
+
+        return new EnvConfig(appDirFile, appId, appBase, certs, appArgs);
+    }
+
+    /** The directory in which the application and metadata is stored. */
+    public final File appDir;
+
+    /** Either {@code null} or an identifier for a secondary application that should be
+      * launched. That app will use {@code appid.class} and {@code appid.apparg} to configure
+      * itself but all other parameters will be the same as the primary app. */
+    public final String appId;
+
+    /** Either {@code null} or fallback {@code appbase} to use if one cannot be read from a
+      * {@code getdown.txt} file during startup. */
+    public final String appBase;
+
+    /** Zero or more signing certificates used to verify the digest file. */
+    public final List<Certificate> certs;
+
+    /** Additional arguments to pass on to launched application. These will be added after the
+      * args in the getdown.txt file. */
+    public final List<String> appArgs;
+
+    public EnvConfig (File appDir) {
+        this(appDir, null, null, Collections.<Certificate>emptyList(),
+             Collections.<String>emptyList());
+    }
+
+    private EnvConfig (File appDir, String appId, String appBase, List<Certificate> certs,
+                       List<String> appArgs) {
+        this.appDir = appDir;
+        this.appId = appId;
+        this.appBase = appBase;
+        this.certs = certs;
+        this.appArgs = appArgs;
+    }
+
+    private static final String USER_HOME_KEY = "${user.home}";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java
new file mode 100644 (file)
index 0000000..b0a1dc9
--- /dev/null
@@ -0,0 +1,135 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarFile;
+
+import com.threerings.getdown.cache.GarbageCollector;
+import com.threerings.getdown.cache.ResourceCache;
+import com.threerings.getdown.util.FileUtil;
+import static com.threerings.getdown.Log.log;
+
+public class PathBuilder
+{
+    /** Name of directory to store cached code files in. */
+    public static final String CODE_CACHE_DIR = ".cache";
+
+    /** Name of directory to store cached native resources in. */
+    public static final String NATIVE_CACHE_DIR = ".ncache";
+
+    /**
+     * Builds either a default or cached classpath based on {@code app}'s configuration.
+     */
+    public static ClassPath buildClassPath (Application app) throws IOException
+    {
+        return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
+    }
+
+    /**
+     * Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
+     * the app directory.
+     */
+    public static ClassPath buildDefaultClassPath (Application app)
+    {
+        LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
+        for (Resource resource: app.getActiveCodeResources()) {
+            classPathEntries.add(resource.getFinalTarget());
+        }
+        return new ClassPath(classPathEntries);
+    }
+
+    /**
+     * Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
+     * a cache directory and then referencing them from there. This avoids problems with
+     * overwriting in-use classpath elements when the application is later updated. This also
+     * "garbage collects" expired caches if necessary.
+     */
+    public static ClassPath buildCachedClassPath (Application app) throws IOException
+    {
+        File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR);
+
+        // a negative value of code_cache_retention_days allows to clean up the cache forcefully
+        long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
+        if (retainMillis != 0L) {
+            GarbageCollector.collect(codeCacheDir, retainMillis);
+        }
+
+        ResourceCache cache = new ResourceCache(codeCacheDir);
+        LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
+        for (Resource resource: app.getActiveCodeResources()) {
+            String digest = app.getDigest(resource);
+            File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest);
+            classPathEntries.add(entry);
+        }
+
+        return new ClassPath(classPathEntries);
+    }
+
+    /**
+     * Builds a {@link ClassPath} instance by first caching all native jars (indicated by
+     * nresource=[native jar]), unpacking them, and referencing the locations of each of the
+     * unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath}
+     *
+     * @param app                   used to determine native jars and related information.
+     * @param addCurrentLibraryPath if true, it adds the locations referenced by
+     *                              {@code System.getProperty("java.library.path")} as well.
+     * @return a classpath instance if at least one native resource was found and unpacked,
+     *         {@code null} if no native resources were used by the application.
+     */
+    public static ClassPath buildLibsPath (Application app,
+                                           boolean addCurrentLibraryPath) throws IOException {
+        List<Resource> resources = app.getNativeResources();
+        if (resources.isEmpty()) {
+            return null;
+        }
+
+        LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
+        File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR);
+        ResourceCache cache = new ResourceCache(nativeCacheDir);
+
+        // negative value forces total garbage collection, 0 avoids garbage collection at all
+        long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
+        if (retainMillis != 0L) {
+            GarbageCollector.collectNative(nativeCacheDir, retainMillis);
+        }
+
+        for (Resource resource : resources) {
+            // Use untruncated cache subdirectory names to avoid overwriting issues when unpacking,
+            // in the off chance that two native jars share a directory AND contain files with the
+            // same names
+            String digest = app.getDigest(resource);
+            File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest);
+            File cachedParent = cachedFile.getParentFile();
+            File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked");
+
+            if (!unpackedIndicator.exists()) {
+                try {
+                    FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false);
+                    unpackedIndicator.createNewFile();
+                } catch (IOException ioe) {
+                    log.warning("Failed to unpack native jar",
+                                "file", cachedFile.getAbsolutePath(), ioe);
+                    // Keep going and unpack the other jars...
+                }
+            }
+
+            nativedirs.add(cachedFile.getParentFile());
+        }
+
+        if (addCurrentLibraryPath) {
+            for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) {
+                nativedirs.add(new File(path));
+            }
+        }
+
+        return new ClassPath(nativedirs);
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java
new file mode 100644 (file)
index 0000000..e70bd4b
--- /dev/null
@@ -0,0 +1,19 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+/**
+ * System property constants associated with Getdown.
+ */
+public class Properties
+{
+    /** This property will be set to "true" on the application when it is being run by getdown. */
+    public static final String GETDOWN = "com.threerings.getdown";
+
+    /** If accepting connections from the launched application, this property
+     * will be set to the connection server port. */
+    public static final String CONNECT_PORT = "com.threerings.getdown.connectPort";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java
new file mode 100644 (file)
index 0000000..adc2d4f
--- /dev/null
@@ -0,0 +1,415 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.*;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import com.threerings.getdown.util.FileUtil;
+import com.threerings.getdown.util.ProgressObserver;
+import com.threerings.getdown.util.StringUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Models a single file resource used by an {@link Application}.
+ */
+public class Resource implements Comparable<Resource>
+{
+    /** Defines special attributes for resources. */
+    public static enum Attr {
+        /** Indicates that the resource should be unpacked. */
+        UNPACK,
+        /** If present, when unpacking a resource, any directories created by the newly
+          * unpacked resource will first be cleared of files before unpacking. */
+        CLEAN,
+        /** Indicates that the resource should be marked executable. */
+        EXEC,
+        /** Indicates that the resource should be downloaded before a UI is displayed. */
+        PRELOAD,
+        /** Indicates that the resource is a jar containing native libs. */
+        NATIVE
+    };
+
+    public static final EnumSet<Attr> NORMAL  = EnumSet.noneOf(Attr.class);
+    public static final EnumSet<Attr> UNPACK  = EnumSet.of(Attr.UNPACK);
+    public static final EnumSet<Attr> EXEC    = EnumSet.of(Attr.EXEC);
+    public static final EnumSet<Attr> PRELOAD = EnumSet.of(Attr.PRELOAD);
+    public static final EnumSet<Attr> NATIVE  = EnumSet.of(Attr.NATIVE);
+
+    /**
+     * Computes the MD5 hash of the supplied file.
+     * @param version the version of the digest protocol to use.
+     */
+    public static String computeDigest (int version, File target, MessageDigest md,
+                                        ProgressObserver obs)
+        throws IOException
+    {
+        md.reset();
+        byte[] buffer = new byte[DIGEST_BUFFER_SIZE];
+        int read;
+
+        boolean isJar = isJar(target.getPath());
+        boolean isPacked200Jar = isPacked200Jar(target.getPath());
+
+        // if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic
+        // manner to properly correlate jardiff patched jars with their unpatched originals
+        if (isJar || isPacked200Jar){
+            File tmpJarFile = null;
+            JarFile jar = null;
+            try {
+                // if this is a compressed jar file, uncompress it to compute the jar file digest
+                if (isPacked200Jar){
+                    tmpJarFile = new File(target.getPath() + ".tmp");
+                    FileUtil.unpackPacked200Jar(target, tmpJarFile);
+                    jar = new JarFile(tmpJarFile);
+                } else{
+                    jar = new JarFile(target);
+                }
+
+                List<JarEntry> entries = Collections.list(jar.entries());
+                Collections.sort(entries, ENTRY_COMP);
+
+                int eidx = 0;
+                for (JarEntry entry : entries) {
+                    // old versions of the digest code skipped metadata
+                    if (version < 2) {
+                        if (entry.getName().startsWith("META-INF")) {
+                            updateProgress(obs, eidx, entries.size());
+                            continue;
+                        }
+                    }
+
+                    try (InputStream in = jar.getInputStream(entry)) {
+                        while ((read = in.read(buffer)) != -1) {
+                            md.update(buffer, 0, read);
+                        }
+                    }
+
+                    updateProgress(obs, eidx, entries.size());
+                }
+
+            } finally {
+                if (jar != null) {
+                    try {
+                        jar.close();
+                    } catch (IOException ioe) {
+                        log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe);
+                    }
+                }
+                if (tmpJarFile != null) {
+                    FileUtil.deleteHarder(tmpJarFile);
+                }
+            }
+
+        } else {
+            long totalSize = target.length(), position = 0L;
+            try (FileInputStream fin = new FileInputStream(target)) {
+                while ((read = fin.read(buffer)) != -1) {
+                    md.update(buffer, 0, read);
+                    position += read;
+                    updateProgress(obs, position, totalSize);
+                }
+            }
+        }
+        return StringUtil.hexlate(md.digest());
+    }
+
+    /**
+     * Creates a resource with the supplied remote URL and local path.
+     */
+    public Resource (String path, URL remote, File local, EnumSet<Attr> attrs)
+    {
+        _path = path;
+        _remote = remote;
+        _local = local;
+        _localNew = new File(local.toString() + "_new");
+        String lpath = _local.getPath();
+        _marker = new File(lpath + "v");
+
+        _attrs = attrs;
+        _isTgz = isTgz(lpath);
+        _isJar = isJar(lpath);
+        _isPacked200Jar = isPacked200Jar(lpath);
+        boolean unpack = attrs.contains(Attr.UNPACK);
+        if (unpack && _isJar) {
+            _unpacked = _local.getParentFile();
+        } else if(unpack && _isTgz) {
+            _unpacked = _local.getParentFile();
+        } else if(unpack && _isPacked200Jar) {
+            String dotJar = ".jar", lname = _local.getName();
+            String uname = lname.substring(0, lname.lastIndexOf(dotJar) + dotJar.length());
+            _unpacked = new File(_local.getParent(), uname);
+        }
+    }
+
+    /**
+     * Returns the path associated with this resource.
+     */
+    public String getPath ()
+    {
+        return _path;
+    }
+
+    /**
+     * Returns the local location of this resource.
+     */
+    public File getLocal ()
+    {
+        return _local;
+    }
+
+    /**
+     * Returns the location of the to-be-installed new version of this resource.
+     */
+    public File getLocalNew ()
+    {
+        return _localNew;
+    }
+
+    /**
+     *  Returns the location of the unpacked resource.
+     */
+    public File getUnpacked ()
+    {
+        return _unpacked;
+    }
+
+    /**
+     *  Returns the final target of this resource, whether it has been unpacked or not.
+     */
+    public File getFinalTarget ()
+    {
+        return shouldUnpack() ? getUnpacked() : getLocal();
+    }
+
+    /**
+     * Returns the remote location of this resource.
+     */
+    public URL getRemote ()
+    {
+        return _remote;
+    }
+
+    /**
+     * Returns true if this resource should be unpacked as a part of the validation process.
+     */
+    public boolean shouldUnpack ()
+    {
+        return _attrs.contains(Attr.UNPACK) && !SysProps.noUnpack();
+    }
+
+    /**
+     * Returns true if this resource should be pre-downloaded.
+     */
+    public boolean shouldPredownload ()
+    {
+        return _attrs.contains(Attr.PRELOAD);
+    }
+
+    /**
+     * Returns true if this resource is a native lib jar.
+     */
+    public boolean isNative ()
+    {
+        return _attrs.contains(Attr.NATIVE);
+    }
+
+    /**
+     * Computes the MD5 hash of this resource's underlying file.
+     * <em>Note:</em> This is both CPU and I/O intensive.
+     * @param version the version of the digest protocol to use.
+     */
+    public String computeDigest (int version, MessageDigest md, ProgressObserver obs)
+        throws IOException
+    {
+        File file;
+        if (_local.toString().toLowerCase(Locale.ROOT).endsWith(Application.CONFIG_FILE)) {
+            file = _local;
+        } else {
+            file = _localNew.exists() ? _localNew : _local;
+        }
+        return computeDigest(version, file, md, obs);
+    }
+
+    /**
+     * Returns true if this resource has an associated "validated" marker
+     * file.
+     */
+    public boolean isMarkedValid ()
+    {
+        if (!_local.exists()) {
+            clearMarker();
+            return false;
+        }
+        return _marker.exists();
+    }
+
+    /**
+     * Creates a "validated" marker file for this resource to indicate
+     * that its MD5 hash has been computed and compared with the value in
+     * the digest file.
+     *
+     * @throws IOException if we fail to create the marker file.
+     */
+    public void markAsValid ()
+        throws IOException
+    {
+        _marker.createNewFile();
+    }
+
+    /**
+     * Removes any "validated" marker file associated with this resource.
+     */
+    public void clearMarker ()
+    {
+        if (_marker.exists() && !FileUtil.deleteHarder(_marker)) {
+            log.warning("Failed to erase marker file '" + _marker + "'.");
+        }
+    }
+
+    /**
+     * Installs the {@code getLocalNew} version of this resource to {@code getLocal}.
+     * @param validate whether or not to mark the resource as valid after installing.
+     */
+    public void install (boolean validate) throws IOException {
+        File source = getLocalNew(), dest = getLocal();
+        log.info("- " + source);
+        if (!FileUtil.renameTo(source, dest)) {
+            throw new IOException("Failed to rename " + source + " to " + dest);
+        }
+        applyAttrs();
+        if (validate) {
+            markAsValid();
+        }
+    }
+
+    /**
+     * Unpacks this resource file into the directory that contains it.
+     */
+    public void unpack () throws IOException
+    {
+        // sanity check
+        if (!_isJar && !_isPacked200Jar && !_isTgz) {
+            throw new IOException("Requested to unpack non-jar/tgz file '" + _local + "'.");
+        }
+        if (_isJar) {
+            try (JarFile jar = new JarFile(_local)) {
+                FileUtil.unpackJar(jar, _unpacked, _attrs.contains(Attr.CLEAN));
+            }
+        } else if (_isTgz) {
+            try (InputStream fi = Files.newInputStream(_local.toPath());
+                         InputStream bi = new BufferedInputStream(fi);
+                         InputStream gzi = new GzipCompressorInputStream(bi);
+                         TarArchiveInputStream tgz = new TarArchiveInputStream(gzi)) {
+                    FileUtil.unpackTgz(tgz, _unpacked, _attrs.contains(Attr.CLEAN));
+            }
+        } else {
+            FileUtil.unpackPacked200Jar(_local, _unpacked);
+        }
+    }
+
+    /**
+     * Applies this resources special attributes: unpacks this resource if needed, marks it as
+     * executable if needed.
+     */
+    public void applyAttrs () throws IOException {
+        if (shouldUnpack()) {
+            unpack();
+        }
+        if (_attrs.contains(Attr.EXEC)) {
+            FileUtil.makeExecutable(_local);
+        }
+    }
+
+    /**
+     * Wipes this resource file along with any "validated" marker file that may be associated with
+     * it.
+     */
+    public void erase ()
+    {
+        clearMarker();
+        if (_local.exists() && !FileUtil.deleteHarder(_local)) {
+            log.warning("Failed to erase resource '" + _local + "'.");
+        }
+    }
+
+    @Override public int compareTo (Resource other) {
+        return _path.compareTo(other._path);
+    }
+
+    @Override public boolean equals (Object other)
+    {
+        if (other instanceof Resource) {
+            return _path.equals(((Resource)other)._path);
+        } else {
+            return false;
+        }
+    }
+
+    @Override public int hashCode ()
+    {
+        return _path.hashCode();
+    }
+
+    @Override public String toString ()
+    {
+        return _path;
+    }
+
+    /** Helper function to simplify the process of reporting progress. */
+    protected static void updateProgress (ProgressObserver obs, long pos, long total)
+    {
+        if (obs != null) {
+            obs.progress((int)(100 * pos / total));
+        }
+    }
+
+    protected static boolean isJar (String path)
+    {
+        return path.endsWith(".jar") || path.endsWith(".jar_new");
+    }
+    
+    protected static boolean isTgz (String path)
+    {
+        return path.endsWith(".tgz") || path.endsWith(".tgz_new");
+    }
+
+    protected static boolean isPacked200Jar (String path)
+    {
+        return path.endsWith(".jar.pack") || path.endsWith(".jar.pack_new") ||
+            path.endsWith(".jar.pack.gz")|| path.endsWith(".jar.pack.gz_new");
+    }
+
+    protected String _path;
+    protected URL _remote;
+    protected File _local, _localNew, _marker, _unpacked;
+    protected EnumSet<Attr> _attrs;
+    protected boolean _isJar, _isPacked200Jar, _isTgz;
+
+    /** Used to sort the entries in a jar file. */
+    protected static final Comparator<JarEntry> ENTRY_COMP = new Comparator<JarEntry>() {
+        @Override public int compare (JarEntry e1, JarEntry e2) {
+            return e1.getName().compareTo(e2.getName());
+        }
+    };
+
+    protected static final int DIGEST_BUFFER_SIZE = 5 * 1025;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java
new file mode 100644 (file)
index 0000000..0d96ecb
--- /dev/null
@@ -0,0 +1,185 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.threerings.getdown.util.VersionUtil;
+
+/**
+ * This class encapsulates all system properties that are read and processed by Getdown. Don't
+ * stick a call to {@code System.getProperty} randomly into the code, put it in here and give it an
+ * accessor so that it's easy to see all of the secret system property arguments that Getdown makes
+ * use of.
+ */
+public class SysProps
+{
+    /** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */
+    public static String appDir () {
+        return System.getProperty("appdir");
+    }
+
+    /** Configures the appid (in lieu of passing it in argv). Usage: {@code -Dappid=foo}. */
+    public static String appId () {
+        return System.getProperty("appid");
+    }
+
+    /** Configures the bootstrap appbase (used in lieu of providing a skeleton getdown.txt, and as
+      * a last resort fallback). Usage: {@code -Dappbase=URL}. */
+    public static String appBase () {
+        return System.getProperty("appbase");
+    }
+
+    /** If true, disables redirection of logging into {@code launcher.log}.
+      * Usage: {@code -Dno_log_redir}. */
+    public static boolean noLogRedir () {
+        return System.getProperty("no_log_redir") != null;
+    }
+
+    /** Overrides the domain on {@code appbase}. Usage: {@code -Dappbase_domain=foo}. */
+    public static String appbaseDomain () {
+        return System.getProperty("appbase_domain");
+    }
+
+    /** Overrides enter {@code appbase}. Usage: {@code -Dappbase_override=URL}. */
+    public static String appbaseOverride () {
+        return System.getProperty("appbase_override");
+    }
+
+    /** If true, Getdown installs the app without ever bringing up a UI (except in the event of an
+      * error). NOTE: it does not launch the app. See {@link #launchInSilent}.
+      * Usage: {@code -Dsilent}. */
+    public static boolean silent () {
+        return System.getProperty("silent") != null;
+    }
+
+    /** Instructs Getdown to install/update the app without ever bringing up a UI (except in the
+      * event of an error), and then launch it.
+      * Usage: {@code -Dsilent=launch}. */
+    public static boolean launchInSilent () {
+        return "launch".equals(System.getProperty("silent"));
+    }
+
+    /**
+     * Instructs Getdown to launch the app without updating it, or ever bringing up a UI (except
+     * in the event of an error).
+     * Usage: {@code -Dsilent=noupdate}.
+     */
+    public static boolean noUpdate() {
+        return "noupdate".equals(System.getProperty("silent"));
+    }
+
+    /** If true, Getdown does not automatically install updates after downloading them. It waits
+      * for the application to call `Getdown.install`.
+      * Usage: {@code -Dno_install}. */
+    public static boolean noInstall () {
+        return System.getProperty("no_install") != null;
+    }
+
+    /** Specifies the delay (in minutes) to wait before starting the update and install process.
+      * Minimum delay is 0 minutes, or no delay (negative values are rounded up to 0 minutes).
+      * Maximum delay is 1 day, or 1440 minutes (larger values are rounded down to 1 day).
+      * Usage: {@code -Ddelay=N}. */
+    public static int startDelay () {
+        return Math.min(Math.max(Integer.getInteger("delay", 0), 0), 60 * 24);
+    }
+
+    /** If true, Getdown will not unpack {@code uresource} jars. Usage: {@code -Dno_unpack}. */
+    public static boolean noUnpack () {
+        return Boolean.getBoolean("no_unpack");
+    }
+
+    /** If true, Getdown will run the application in the same VM in which Getdown is running. If
+      * false (the default), Getdown will fork a new VM. Note that reusing the same VM prevents
+      * Getdown from configuring some launch-time-only VM parameters (like -mxN etc.).
+      * Usage: {@code -Ddirect}. */
+    public static boolean direct () {
+        return Boolean.getBoolean("direct");
+    }
+
+    /** Specifies the connection timeout (in seconds) to use when downloading control files from
+      * the server. This is chiefly useful when you are running in versionless mode and want Getdown
+      * to more quickly timeout its startup update check if the server with which it is
+      * communicating is not available. Usage: {@code -Dconnect_timeout=N}. */
+    public static int connectTimeout () {
+        return Integer.getInteger("connect_timeout", 0);
+    }
+
+    /** Specifies the read timeout (in seconds) to use when downloading all files from the server.
+      * The default is 30 seconds, meaning that if a download stalls for more than 30 seconds, the
+      * update process wil fail. Setting the timeout to zero (or a negative value) will disable it.
+      * Usage: {@code -Dread_timeout=N}. */
+    public static int readTimeout () {
+        return Integer.getInteger("read_timeout", 30);
+    }
+
+    /** Returns the number of threads used to perform digesting and verifying operations in
+      * parallel. Usage: {@code -Dthread_pool_size=N} */
+    public static int threadPoolSize () {
+        int defaultSize = Math.max(Runtime.getRuntime().availableProcessors()-1, 1);
+        return Integer.getInteger("thread_pool_size", defaultSize);
+    }
+
+    /** Parses a Java version system property using the supplied regular expression. The numbers
+      * extracted from the regexp will be placed in each consecutive hundreds position in the
+      * returned value.
+      *
+      * <p>For example, {@code java.version} takes the form {@code 1.8.0_31}, and with the regexp
+      * {@code (\d+)\.(\d+)\.(\d+)(_\d+)?} we would parse {@code 1, 8, 0, 31} and combine them into
+      * the final value {@code 1080031}.
+      *
+      * <p>Note that non-numeric characters matched by the regular expression will simply be
+      * ignored, and optional groups which do not match are treated as zero in the final version
+      * calculation.
+      *
+      * <p>One can instead parse {@code java.runtime.version} which takes the form {@code
+      * 1.8.0_31-b13}. Using regexp {@code (\d+)\.(\d+)\.(\d+)_(\d+)-b(\d+)} we would parse
+      * {@code 1, 8, 0, 31, 13} and combine them into a final value {@code 108003113}.
+      *
+      * <p>Other (or future) JVMs may provide different version properties which can be parsed as
+      * desired using this general scheme as long as the numbers appear from left to right in order
+      * of significance.
+      *
+      * @throws IllegalArgumentException if no system named {@code propName} exists, or if
+      * {@code propRegex} does not match the returned version string.
+      */
+    public static long parseJavaVersion (String propName, String propRegex) {
+        String verstr = System.getProperty(propName);
+        if (verstr == null) throw new IllegalArgumentException(
+            "No system property '" + propName + "'.");
+
+        long vers = VersionUtil.parseJavaVersion(propRegex, verstr);
+        if (vers == 0L) throw new IllegalArgumentException(
+            "Regexp '" + propRegex + "' does not match '" + verstr + "' (from " + propName + ")");
+        return vers;
+    }
+
+    /**
+     * Applies {@code appbase_override} or {@code appbase_domain} if they are set.
+     */
+    public static String overrideAppbase (String appbase) {
+        String appbaseOverride = appbaseOverride();
+        if (appbaseOverride != null) {
+            return appbaseOverride;
+        } else {
+            return replaceDomain(appbase);
+        }
+    }
+
+    /**
+     * If appbase_domain property is set, replace the domain on the provided string.
+     */
+    public static String replaceDomain (String appbase)
+    {
+        String appbaseDomain = appbaseDomain();
+        if (appbaseDomain != null) {
+            Matcher m = Pattern.compile("(https?://[^/]+)(.*)").matcher(appbase);
+            appbase = m.replaceAll(appbaseDomain + "$2");
+        }
+        return appbase;
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java
new file mode 100644 (file)
index 0000000..6033e2f
--- /dev/null
@@ -0,0 +1,229 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.net;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.threerings.getdown.data.Resource;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Handles the download of a collection of files, first issuing HTTP head requests to obtain size
+ * information and then downloading the files individually, reporting progress back via protected
+ * callback methods. <em>Note:</em> these methods are all called arbitrary download threads, so
+ * implementors must take care to only execute thread-safe code or simply pass a message to the AWT
+ * thread, for example.
+ */
+public abstract class Downloader
+{
+    /**
+     * Start the downloading process.
+     * @param resources the resources to download.
+     * @param maxConcurrent the maximum number of concurrent downloads allowed.
+     * @return true if the download completed, false if it was aborted (via {@link #abort}).
+     */
+    public boolean download (Collection<Resource> resources, int maxConcurrent)
+    {
+        // first compute the total size of our download
+        resolvingDownloads();
+        for (Resource rsrc : resources) {
+            try {
+                _sizes.put(rsrc, Math.max(checkSize(rsrc), 0L));
+            } catch (IOException ioe) {
+                downloadFailed(rsrc, ioe);
+            }
+        }
+
+        long totalSize = sum(_sizes.values());
+        log.info("Downloading " + resources.size() + " resources",
+                 "totalBytes", totalSize, "maxConcurrent", maxConcurrent);
+
+        // make a note of the time at which we started the download
+        _start = System.currentTimeMillis();
+
+        // start the downloads
+        ExecutorService exec = Executors.newFixedThreadPool(maxConcurrent);
+        for (final Resource rsrc : resources) {
+            // make sure the resource's target directory exists
+            File parent = new File(rsrc.getLocal().getParent());
+            if (!parent.exists() && !parent.mkdirs()) {
+                log.warning("Failed to create target directory for resource '" + rsrc + "'.");
+            }
+
+            exec.execute(new Runnable() {
+                @Override public void run () {
+                    try {
+                        if (_state != State.ABORTED) {
+                            download(rsrc);
+                        }
+                    } catch (IOException ioe) {
+                        _state = State.FAILED;
+                        downloadFailed(rsrc, ioe);
+                    }
+                }
+            });
+        }
+        exec.shutdown();
+
+        // wait for the downloads to complete
+        try {
+            exec.awaitTermination(10, TimeUnit.DAYS);
+
+            // report download completion if we did not already do so via our final resource
+            if (_state == State.DOWNLOADING) {
+                downloadProgress(100, 0);
+            }
+
+        } catch (InterruptedException ie) {
+            exec.shutdownNow();
+            downloadFailed(null, ie);
+        }
+
+        return _state != State.ABORTED;
+    }
+
+    /**
+     * Aborts the in-progress download.
+     */
+    public void abort () {
+        _state = State.ABORTED;
+    }
+
+    /**
+     * Called before the downloader begins the series of HTTP head requests to determine the
+     * size of the files it needs to download.
+     */
+    protected void resolvingDownloads () {}
+
+    /**
+     * Reports ongoing progress toward completion of the overall downloading task. One call is
+     * guaranteed to be made reporting 100% completion if the download is not aborted and no
+     * resources fail.
+     *
+     * @param percent the percent completion of the complete download process (based on total bytes
+     * downloaded versus total byte size of all resources).
+     * @param remaining the estimated download time remaining in seconds, or {@code -1} if the time
+     * can not yet be determined.
+     */
+    protected void downloadProgress (int percent, long remaining) {}
+
+    /**
+     * Called if a failure occurs while downloading a resource. No progress will be reported after
+     * a download fails, but additional download failures may be reported.
+     *
+     * @param rsrc the resource that failed to download, or null if the download failed due to
+     * thread interruption.
+     * @param cause the exception detailing the failure.
+     */
+    protected void downloadFailed (Resource rsrc, Exception cause) {}
+
+    /**
+     * Performs the protocol-specific portion of checking download size.
+     */
+    protected abstract long checkSize (Resource rsrc) throws IOException;
+
+    /**
+     * Periodically called by the protocol-specific downloaders to update their progress. This
+     * should be called at least once for each resource to be downloaded, with the total downloaded
+     * size for that resource. It can also be called periodically along the way for each resource
+     * to communicate incremental progress.
+     *
+     * @param rsrc the resource currently being downloaded.
+     * @param currentSize the number of bytes currently downloaded for said resource.
+     * @param actualSize the size reported for this resource now that we're actually downloading
+     * it. Some web servers lie about Content-length when doing a HEAD request, so by reporting
+     * updated sizes here we can recover from receiving bogus information in the earlier
+     * {@link #checkSize} phase.
+     */
+    protected synchronized void reportProgress (Resource rsrc, long currentSize, long actualSize)
+    {
+        // update the actual size for this resource (but don't let it shrink)
+        _sizes.put(rsrc, actualSize = Math.max(actualSize, _sizes.get(rsrc)));
+
+        // update the current downloaded size for said resource; don't allow the downloaded bytes
+        // to exceed the original claimed size of the resource, otherwise our progress will get
+        // booched and we'll end up back on the Daily WTF: http://tinyurl.com/29wt4oq
+        _downloaded.put(rsrc, Math.min(actualSize, currentSize));
+
+        // notify the observer if it's been sufficiently long since our last notification
+        long now = System.currentTimeMillis();
+        if ((now - _lastUpdate) >= UPDATE_DELAY) {
+            _lastUpdate = now;
+
+            // total up our current and total bytes
+            long downloaded = sum(_downloaded.values());
+            long totalSize = sum(_sizes.values());
+
+            // compute our bytes per second
+            long secs = (now - _start) / 1000L;
+            long bps = (secs == 0) ? 0 : (downloaded / secs);
+
+            // compute our percentage completion
+            int pctdone = (totalSize == 0) ? 0 : (int)((downloaded * 100f) / totalSize);
+
+            // estimate our time remaining
+            long remaining = (bps <= 0 || totalSize == 0) ? -1 : (totalSize - downloaded) / bps;
+
+            // if we're complete or failed, when we don't want to report again
+            if (_state == State.DOWNLOADING) {
+                if (pctdone == 100) _state = State.COMPLETE;
+                downloadProgress(pctdone, remaining);
+            }
+        }
+    }
+
+    /**
+     * Sums the supplied values.
+     */
+    protected static long sum (Iterable<Long> values)
+    {
+        long acc = 0L;
+        for (Long value : values) {
+            acc += value;
+        }
+        return acc;
+    }
+
+    protected enum State { DOWNLOADING, COMPLETE, FAILED, ABORTED }
+
+    /**
+     * Accomplishes the copying of the resource from remote location to local location using
+     * protocol-specific code. This method should periodically check whether {@code _state} is set
+     * to aborted and abort any in-progress download if so.
+     */
+    protected abstract void download (Resource rsrc) throws IOException;
+
+    /** The reported sizes of our resources. */
+    protected Map<Resource, Long> _sizes = new HashMap<>();
+
+    /** The bytes downloaded for each resource. */
+    protected Map<Resource, Long> _downloaded = new HashMap<>();
+
+    /** The time at which the file transfer began. */
+    protected long _start;
+
+    /** The current transfer rate in bytes per second. */
+    protected long _bytesPerSecond;
+
+    /** The time at which the last progress update was posted to the progress observer. */
+    protected long _lastUpdate;
+
+    /** A wee state machine to ensure we call our callbacks sanely. */
+    protected volatile State _state = State.DOWNLOADING;
+
+    /** The delay in milliseconds between notifying progress observers of file download
+      * progress. */
+    protected static final long UPDATE_DELAY = 500L;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java
new file mode 100644 (file)
index 0000000..a7a3287
--- /dev/null
@@ -0,0 +1,115 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.net;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+
+import com.threerings.getdown.data.Resource;
+import com.threerings.getdown.util.ConnectionUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Implements downloading files over HTTP
+ */
+public class HTTPDownloader extends Downloader
+{
+    public HTTPDownloader (Proxy proxy)
+    {
+        _proxy = proxy;
+    }
+
+    @Override protected long checkSize (Resource rsrc) throws IOException
+    {
+        URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
+        try {
+            // if we're accessing our data via HTTP, we only need a HEAD request
+            if (conn instanceof HttpURLConnection) {
+                HttpURLConnection hcon = (HttpURLConnection)conn;
+                hcon.setRequestMethod("HEAD");
+                hcon.connect();
+                // make sure we got a satisfactory response code
+                if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                    throw new IOException("Unable to check up-to-date for " +
+                                          rsrc.getRemote() + ": " + hcon.getResponseCode());
+                }
+            }
+            return conn.getContentLength();
+
+        } finally {
+            // let it be known that we're done with this connection
+            conn.getInputStream().close();
+        }
+    }
+
+    @Override protected void download (Resource rsrc) throws IOException
+    {
+        // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
+        // system property
+        if (true) {
+            // download the resource from the specified URL
+            URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
+            conn.connect();
+
+            // make sure we got a satisfactory response code
+            if (conn instanceof HttpURLConnection) {
+                HttpURLConnection hcon = (HttpURLConnection)conn;
+                if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                    throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " +
+                                          hcon.getResponseCode());
+                }
+            }
+            long actualSize = conn.getContentLength();
+            log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
+            long currentSize = 0L;
+            byte[] buffer = new byte[4*4096];
+            try (InputStream in = conn.getInputStream();
+                 FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
+
+                // TODO: look to see if we have a download info file
+                // containing info on potentially partially downloaded data;
+                // if so, use a "Range: bytes=HAVE-" header.
+
+                // read in the file data
+                int read;
+                while ((read = in.read(buffer)) != -1) {
+                    // abort the download if the downloader is aborted
+                    if (_state == State.ABORTED) {
+                        break;
+                    }
+                    // write it out to our local copy
+                    out.write(buffer, 0, read);
+                    // note that we've downloaded some data
+                    currentSize += read;
+                    reportProgress(rsrc, currentSize, actualSize);
+                }
+            }
+
+        } else {
+            log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
+            File localNew = rsrc.getLocalNew();
+            try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream());
+                 FileOutputStream fos = new FileOutputStream(localNew)) {
+                // TODO: more work is needed here, transferFrom can fail to transfer the entire
+                // file, in which case it's not clear what we're supposed to do.. call it again?
+                // will it repeatedly fail?
+                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+                reportProgress(rsrc, localNew.length(), localNew.length());
+            }
+        }
+    }
+
+    protected final Proxy _proxy;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java
new file mode 100644 (file)
index 0000000..22446ec
--- /dev/null
@@ -0,0 +1,32 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.spi;
+
+/**
+ * A service provider interface that handles the storage of proxy credentials.
+ */
+public interface ProxyAuth
+{
+    /** Credentials for a proxy server. */
+    public static class Credentials {
+        public final String username;
+        public final String password;
+        public Credentials (String username, String password) {
+            this.username = username;
+            this.password = password;
+        }
+    }
+
+    /**
+     * Loads the credentials for the app installed in {@code appDir}.
+     */
+    public Credentials loadCredentials (String appDir);
+
+    /**
+     * Encrypts and saves the credentials for the app installed in {@code appDir}.
+     */
+    public void saveCredentials (String appDir, String username, String password);
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java
new file mode 100644 (file)
index 0000000..c2e740b
--- /dev/null
@@ -0,0 +1,232 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import java.security.MessageDigest;
+
+import com.threerings.getdown.data.Application;
+import com.threerings.getdown.data.Digest;
+import com.threerings.getdown.data.EnvConfig;
+import com.threerings.getdown.data.Resource;
+import com.threerings.getdown.util.FileUtil;
+import com.threerings.getdown.util.StreamUtil;
+
+/**
+ * Generates patch files between two particular revisions of an
+ * application. The differences between all the files in the two
+ * revisions are bundled into a single patch file which is placed into the
+ * target version directory.
+ */
+public class Differ
+{
+    /**
+     * Creates a single patch file that contains the differences between
+     * the two specified application directories. The patch file will be
+     * created in the <code>nvdir</code> directory with name
+     * <code>patchV.dat</code> where V is the old application version.
+     */
+    public void createDiff (File nvdir, File ovdir, boolean verbose)
+        throws IOException
+    {
+        // sanity check
+        String nvers = nvdir.getName();
+        String overs = ovdir.getName();
+        try {
+            if (Long.parseLong(nvers) <= Long.parseLong(overs)) {
+                String err = "New version (" + nvers + ") must be greater " +
+                    "than old version (" + overs + ").";
+                throw new IOException(err);
+            }
+        } catch (NumberFormatException nfe) {
+            throw new IOException("Non-numeric versions? [nvers=" + nvers +
+                                  ", overs=" + overs + "].");
+        }
+
+        Application oapp = new Application(new EnvConfig(ovdir));
+        oapp.init(false);
+        ArrayList<Resource> orsrcs = new ArrayList<>();
+        orsrcs.addAll(oapp.getCodeResources());
+        orsrcs.addAll(oapp.getResources());
+
+        Application napp = new Application(new EnvConfig(nvdir));
+        napp.init(false);
+        ArrayList<Resource> nrsrcs = new ArrayList<>();
+        nrsrcs.addAll(napp.getCodeResources());
+        nrsrcs.addAll(napp.getResources());
+
+        // first create a patch for the main application
+        File patch = new File(nvdir, "patch" + overs + ".dat");
+        createPatch(patch, orsrcs, nrsrcs, verbose);
+
+        // next create patches for any auxiliary resource groups
+        for (Application.AuxGroup ag : napp.getAuxGroups()) {
+            orsrcs = new ArrayList<>();
+            Application.AuxGroup oag = oapp.getAuxGroup(ag.name);
+            if (oag != null) {
+                orsrcs.addAll(oag.codes);
+                orsrcs.addAll(oag.rsrcs);
+            }
+            nrsrcs = new ArrayList<>();
+            nrsrcs.addAll(ag.codes);
+            nrsrcs.addAll(ag.rsrcs);
+            patch = new File(nvdir, "patch-" + ag.name + overs + ".dat");
+            createPatch(patch, orsrcs, nrsrcs, verbose);
+        }
+    }
+
+    protected void createPatch (File patch, ArrayList<Resource> orsrcs,
+                                ArrayList<Resource> nrsrcs, boolean verbose)
+        throws IOException
+    {
+        int version = Digest.VERSION;
+        MessageDigest md = Digest.getMessageDigest(version);
+        try (FileOutputStream fos = new FileOutputStream(patch);
+             BufferedOutputStream buffered = new BufferedOutputStream(fos);
+             JarOutputStream jout = new JarOutputStream(buffered)) {
+
+            // for each file in the new application, it either already exists
+            // in the old application, or it is new
+            for (Resource rsrc : nrsrcs) {
+                int oidx = orsrcs.indexOf(rsrc);
+                Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx);
+                if (orsrc != null) {
+                    // first see if they are the same
+                    String odig = orsrc.computeDigest(version, md, null);
+                    String ndig = rsrc.computeDigest(version, md, null);
+                    if (odig.equals(ndig)) {
+                        if (verbose) {
+                            System.out.println("Unchanged: " + rsrc.getPath());
+                        }
+                        // by leaving it out, it will be left as is during the
+                        // patching process
+                        continue;
+                    }
+
+                    // otherwise potentially create a jar diff
+                    if (rsrc.getPath().endsWith(".jar")) {
+                        if (verbose) {
+                            System.out.println("JarDiff: " + rsrc.getPath());
+                        }
+                        // here's a juicy one: JarDiff blindly pulls ZipEntry
+                        // objects out of one jar file and stuffs them into
+                        // another without clearing out things like the
+                        // compressed size, so if, for whatever reason (like
+                        // different JRE versions or phase of the moon) the
+                        // compressed size in the old jar file is different
+                        // than the compressed size generated when creating the
+                        // jardiff jar file, ZipOutputStream will choke and
+                        // we'll be hosed; so we recreate the jar files in
+                        // their entirety before running jardiff on 'em
+                        File otemp = rebuildJar(orsrc.getLocal());
+                        File temp = rebuildJar(rsrc.getLocal());
+                        jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.PATCH));
+                        jarDiff(otemp, temp, jout);
+                        FileUtil.deleteHarder(otemp);
+                        FileUtil.deleteHarder(temp);
+                        continue;
+                    }
+                }
+
+                if (verbose) {
+                    System.out.println("Addition: " + rsrc.getPath());
+                }
+                jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.CREATE));
+                pipe(rsrc.getLocal(), jout);
+            }
+
+            // now any file remaining in orsrcs needs to be removed
+            for (Resource rsrc : orsrcs) {
+                // add an entry with the resource name and the deletion suffix
+                if (verbose) {
+                    System.out.println("Removal: " + rsrc.getPath());
+                }
+                jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.DELETE));
+            }
+
+            System.out.println("Created patch file: " + patch);
+
+        } catch (IOException ioe) {
+            FileUtil.deleteHarder(patch);
+            throw ioe;
+        }
+    }
+
+    protected File rebuildJar (File target)
+        throws IOException
+    {
+        File temp = File.createTempFile("differ", "jar");
+        try (JarFile jar = new JarFile(target);
+             FileOutputStream tempFos = new FileOutputStream(temp);
+             BufferedOutputStream tempBos = new BufferedOutputStream(tempFos);
+             JarOutputStream jout = new JarOutputStream(tempBos)) {
+            byte[] buffer = new byte[4096];
+            for (Enumeration< JarEntry > iter = jar.entries(); iter.hasMoreElements();) {
+                JarEntry entry = iter.nextElement();
+                entry.setCompressedSize(-1);
+                jout.putNextEntry(entry);
+                try (InputStream in = jar.getInputStream(entry)) {
+                    int size = in.read(buffer);
+                    while (size != -1) {
+                        jout.write(buffer, 0, size);
+                        size = in.read(buffer);
+                    }
+                }
+            }
+        }
+        return temp;
+    }
+
+    protected void jarDiff (File ofile, File nfile, JarOutputStream jout)
+        throws IOException
+    {
+        JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false);
+    }
+
+    public static void main (String[] args)
+    {
+        if (args.length < 2) {
+            System.err.println(
+                "Usage: Differ [-verbose] new_vers_dir old_vers_dir");
+            System.exit(255);
+        }
+        Differ differ = new Differ();
+        boolean verbose = false;
+        int aidx = 0;
+        if (args[0].equals("-verbose")) {
+            verbose = true;
+            aidx++;
+        }
+        try {
+            differ.createDiff(new File(args[aidx++]),
+                              new File(args[aidx++]), verbose);
+        } catch (IOException ioe) {
+            System.err.println("Error: " + ioe.getMessage());
+            System.exit(255);
+        }
+    }
+
+    protected static void pipe (File file, JarOutputStream jout)
+        throws IOException
+    {
+        try (FileInputStream fin = new FileInputStream(file)) {
+            StreamUtil.copy(fin, jout);
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java
new file mode 100644 (file)
index 0000000..b04a653
--- /dev/null
@@ -0,0 +1,129 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Signature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.threerings.getdown.data.Application;
+import com.threerings.getdown.data.Digest;
+import com.threerings.getdown.data.EnvConfig;
+import com.threerings.getdown.data.Resource;
+import com.threerings.getdown.util.Base64;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Handles the generation of the digest.txt file.
+ */
+public class Digester
+{
+    /**
+     * A command line entry point for the digester.
+     */
+    public static void main (String[] args)
+        throws IOException, GeneralSecurityException
+    {
+        switch (args.length) {
+        case 1:
+            createDigests(new File(args[0]), null, null, null);
+            break;
+        case 4:
+            createDigests(new File(args[0]), new File(args[1]), args[2], args[3]);
+            break;
+        default:
+            System.err.println("Usage: Digester app_dir [keystore_path password alias]");
+            System.exit(255);
+        }
+    }
+
+    /**
+     * Creates digest file(s) and optionally signs them if {@code keystore} is not null.
+     */
+    public static void createDigests (File appdir, File keystore, String password, String alias)
+        throws IOException, GeneralSecurityException
+    {
+        for (int version = 1; version <= Digest.VERSION; version++) {
+            createDigest(version, appdir);
+            if (keystore != null) {
+                signDigest(version, appdir, keystore, password, alias);
+            }
+        }
+    }
+
+    /**
+     * Creates a digest file in the specified application directory.
+     */
+    public static void createDigest (int version, File appdir)
+        throws IOException
+    {
+        File target = new File(appdir, Digest.digestFile(version));
+        System.out.println("Generating digest file '" + target + "'...");
+
+        // create our application and instruct it to parse its business
+        Application app = new Application(new EnvConfig(appdir));
+        app.init(false);
+
+        List<Resource> rsrcs = new ArrayList<>();
+        rsrcs.add(app.getConfigResource());
+        rsrcs.addAll(app.getCodeResources());
+        rsrcs.addAll(app.getResources());
+        for (Application.AuxGroup ag : app.getAuxGroups()) {
+            rsrcs.addAll(ag.codes);
+            rsrcs.addAll(ag.rsrcs);
+        }
+
+        // now generate the digest file
+        Digest.createDigest(version, rsrcs, target);
+    }
+
+    /**
+     * Creates a digest file in the specified application directory.
+     */
+    public static void signDigest (int version, File appdir,
+                                   File storePath, String storePass, String storeAlias)
+        throws IOException, GeneralSecurityException
+    {
+        String filename = Digest.digestFile(version);
+        File inputFile = new File(appdir, filename);
+        File signatureFile = new File(appdir, filename + Application.SIGNATURE_SUFFIX);
+
+        try (FileInputStream storeInput = new FileInputStream(storePath);
+             FileInputStream dataInput = new FileInputStream(inputFile);
+             FileOutputStream signatureOutput = new FileOutputStream(signatureFile)) {
+
+            // initialize the keystore
+            KeyStore store = KeyStore.getInstance("JKS");
+            store.load(storeInput, storePass.toCharArray());
+            PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray());
+
+            // sign the digest file
+            String algo = Digest.sigAlgorithm(version);
+            Signature sig = Signature.getInstance(algo);
+            byte[] buffer = new byte[8192];
+            int length;
+
+            sig.initSign(key);
+            while ((length = dataInput.read(buffer)) != -1) {
+                sig.update(buffer, 0, length);
+            }
+
+            // Write out the signature
+            String signed = Base64.encodeToString(sig.sign(), Base64.DEFAULT);
+            signatureOutput.write(signed.getBytes(UTF_8));
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
new file mode 100644 (file)
index 0000000..1cea0ea
--- /dev/null
@@ -0,0 +1,449 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+/*
+ * @(#)JarDiff.java 1.7 05/11/17
+ *
+ * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.threerings.getdown.tools;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * JarDiff is able to create a jar file containing the delta between two jar files (old and new).
+ * The delta jar file can then be applied to the old jar file to reconstruct the new jar file.
+ *
+ * <p> Refer to the JNLP spec for details on how this is done.
+ *
+ * @version 1.13, 06/26/03
+ */
+public class JarDiff implements JarDiffCodes
+{
+    private static final int DEFAULT_READ_SIZE = 2048;
+    private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+    private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+
+    // The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java
+    // and other stuff here. Instead, we use an explicit _debug flag.
+    private static boolean _debug;
+
+    /**
+     * Creates a patch from the two passed in files, writing the result to <code>os</code>.
+     */
+    public static void createPatch (String oldPath, String newPath,
+                                    OutputStream os, boolean minimal) throws IOException
+    {
+        try (JarFile2 oldJar = new JarFile2(oldPath);
+             JarFile2 newJar = new JarFile2(newPath)) {
+
+            HashMap<String,String> moved = new HashMap<>();
+            HashSet<String> implicit = new HashSet<>();
+            HashSet<String> moveSrc = new HashSet<>();
+            HashSet<String> newEntries = new HashSet<>();
+
+            // FIRST PASS
+            // Go through the entries in new jar and
+            // determine which files are candidates for implicit moves
+            // ( files that has the same filename and same content in old.jar
+            // and new.jar )
+            // and for files that cannot be implicitly moved, we will either
+            // find out whether it is moved or new (modified)
+            for (JarEntry newEntry : newJar) {
+                String newname = newEntry.getName();
+
+                // Return best match of contents, will return a name match if possible
+                String oldname = oldJar.getBestMatch(newJar, newEntry);
+                if (oldname == null) {
+                    // New or modified entry
+                    if (_debug) {
+                        System.out.println("NEW: "+ newname);
+                    }
+                    newEntries.add(newname);
+                } else {
+                    // Content already exist - need to do a move
+
+                    // Should do implicit move? Yes, if names are the same, and
+                    // no move command already exist from oldJar
+                    if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
+                        if (_debug) {
+                            System.out.println(newname + " added to implicit set!");
+                        }
+                        implicit.add(newname);
+                    } else {
+                        // The 1.0.1/1.0 JarDiffPatcher cannot handle
+                        // multiple MOVE command with same src.
+                        // The work around here is if we are going to generate
+                        // a MOVE command with duplicate src, we will
+                        // instead add the target as a new file.  This way
+                        // the jardiff can be applied by 1.0.1/1.0
+                        // JarDiffPatcher also.
+                        if (!minimal && (implicit.contains(oldname) ||
+                                         moveSrc.contains(oldname) )) {
+
+                            // generate non-minimal jardiff
+                            // for backward compatibility
+
+                            if (_debug) {
+
+                                System.out.println("NEW: "+ newname);
+                            }
+                            newEntries.add(newname);
+                        } else {
+                            // Use newname as key, since they are unique
+                            if (_debug) {
+                                System.err.println("moved.put " + newname + " " + oldname);
+                            }
+                            moved.put(newname, oldname);
+                            moveSrc.add(oldname);
+                        }
+                        // Check if this disables an implicit 'move <oldname> <oldname>'
+                        if (implicit.contains(oldname) && minimal) {
+
+                            if (_debug) {
+                                System.err.println("implicit.remove " + oldname);
+
+                                System.err.println("moved.put " + oldname + " " + oldname);
+
+                            }
+                            implicit.remove(oldname);
+                            moved.put(oldname, oldname);
+                            moveSrc.add(oldname);
+                        }
+                    }
+                }
+            }
+
+            // SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
+            // <source of move commands> - <new or modified entries>
+            ArrayList<String> deleted = new ArrayList<>();
+            for (JarEntry oldEntry : oldJar) {
+                String oldName = oldEntry.getName();
+                if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
+                    && !newEntries.contains(oldName)) {
+                    if (_debug) {
+                        System.err.println("deleted.add " + oldName);
+                    }
+                    deleted.add(oldName);
+                }
+            }
+
+            //DEBUG
+            if (_debug) {
+                //DEBUG:  print out moved map
+                System.out.println("MOVED MAP!!!");
+                for (Map.Entry<String,String> entry : moved.entrySet()) {
+                    System.out.println(entry);
+                }
+
+                //DEBUG:  print out IMOVE map
+                System.out.println("IMOVE MAP!!!");
+                for (String newName : implicit) {
+                    System.out.println("key is " + newName);
+                }
+            }
+
+            JarOutputStream jos = new JarOutputStream(os);
+
+            // Write out all the MOVEs and REMOVEs
+            createIndex(jos, deleted, moved);
+
+            // Put in New and Modified entries
+            for (String newName : newEntries) {
+                if (_debug) {
+                    System.out.println("New File: " + newName);
+                }
+                writeEntry(jos, newJar.getEntryByName(newName), newJar);
+            }
+
+            jos.finish();
+//            jos.close();
+        }
+    }
+
+    /**
+     * Writes the index file out to <code>jos</code>.
+     * <code>oldEntries</code> gives the names of the files that were removed,
+     * <code>movedMap</code> maps from the new name to the old name.
+     */
+    private static void createIndex (JarOutputStream jos, List<String> oldEntries,
+                                     Map<String,String> movedMap)
+        throws IOException
+    {
+        StringWriter writer = new StringWriter();
+        writer.write(VERSION_HEADER);
+        writer.write("\r\n");
+
+        // Write out entries that have been removed
+        for (String name : oldEntries) {
+            writer.write(REMOVE_COMMAND);
+            writer.write(" ");
+            writeEscapedString(writer, name);
+            writer.write("\r\n");
+        }
+
+        // And those that have moved
+        for (String newName : movedMap.keySet()) {
+            String oldName = movedMap.get(newName);
+            writer.write(MOVE_COMMAND);
+            writer.write(" ");
+            writeEscapedString(writer, oldName);
+            writer.write(" ");
+            writeEscapedString(writer, newName);
+            writer.write("\r\n");
+        }
+
+        jos.putNextEntry(new JarEntry(INDEX_NAME));
+        byte[] bytes = writer.toString().getBytes(UTF_8);
+        jos.write(bytes, 0, bytes.length);
+    }
+
+    protected static Writer writeEscapedString (Writer writer, String string)
+        throws IOException
+    {
+        int index = 0;
+        int last = 0;
+        char[] chars = null;
+
+        while ((index = string.indexOf(' ', index)) != -1) {
+            if (last != index) {
+                if (chars == null) {
+                    chars = string.toCharArray();
+                }
+                writer.write(chars, last, index - last);
+            }
+            last = index;
+            index++;
+            writer.write('\\');
+        }
+        if (last != 0 && chars != null) {
+            writer.write(chars, last, chars.length - last);
+        }
+        else {
+            // no spaces
+            writer.write(string);
+        }
+
+        return writer;
+    }
+
+    private static void writeEntry (JarOutputStream jos, JarEntry entry, JarFile2 file)
+        throws IOException
+    {
+        try (InputStream data = file.getJarFile().getInputStream(entry)) {
+            jos.putNextEntry(entry);
+            int size = data.read(newBytes);
+            while (size != -1) {
+                jos.write(newBytes, 0, size);
+                size = data.read(newBytes);
+            }
+        }
+    }
+
+    /**
+     * JarFile2 wraps a JarFile providing some convenience methods.
+     */
+    private static class JarFile2 implements Iterable<JarEntry>, Closeable
+    {
+        private JarFile _jar;
+        private List<JarEntry> _entries;
+        private HashMap<String,JarEntry> _nameToEntryMap;
+        private HashMap<Long,LinkedList<JarEntry>> _crcToEntryMap;
+
+        public JarFile2 (String path) throws IOException {
+            _jar = new JarFile(new File(path));
+            index();
+        }
+
+        public JarFile getJarFile () {
+            return _jar;
+        }
+
+        // from interface Iterable<JarEntry>
+        @Override
+        public Iterator<JarEntry> iterator () {
+            return _entries.iterator();
+        }
+
+        public JarEntry getEntryByName (String name) {
+            return _nameToEntryMap.get(name);
+        }
+
+        /**
+         * Returns true if the two InputStreams differ.
+         */
+        private static boolean differs (InputStream oldIS, InputStream newIS) throws IOException {
+            int newSize = 0;
+            int oldSize;
+            int total = 0;
+            boolean retVal = false;
+
+            while (newSize != -1) {
+                newSize = newIS.read(newBytes);
+                oldSize = oldIS.read(oldBytes);
+
+                if (newSize != oldSize) {
+                    if (_debug) {
+                        System.out.println("\tread sizes differ: " + newSize +
+                                           " " + oldSize + " total " + total);
+                    }
+                    retVal = true;
+                    break;
+                }
+                if (newSize > 0) {
+                    while (--newSize >= 0) {
+                        total++;
+                        if (newBytes[newSize] != oldBytes[newSize]) {
+                            if (_debug) {
+                                System.out.println("\tbytes differ at " +
+                                                   total);
+                            }
+                            retVal = true;
+                            break;
+                        }
+                        if ( retVal ) {
+                            //Jump out
+                            break;
+                        }
+                        newSize = 0;
+                    }
+                }
+            }
+
+            return retVal;
+        }
+
+        public String getBestMatch (JarFile2 file, JarEntry entry) throws IOException {
+            // check for same name and same content, return name if found
+            if (contains(file, entry)) {
+                return (entry.getName());
+            }
+
+            // return name of same content file or null
+            return (hasSameContent(file,entry));
+        }
+
+        public boolean contains (JarFile2 f, JarEntry e) throws IOException {
+
+            JarEntry thisEntry = getEntryByName(e.getName());
+
+            // Look up name in 'this' Jar2File - if not exist return false
+            if (thisEntry == null)
+                return false;
+
+            // Check CRC - if no match - return false
+            if (thisEntry.getCrc() != e.getCrc())
+                return false;
+
+            // Check contents - if no match - return false
+            try (InputStream oldIS = getJarFile().getInputStream(thisEntry);
+                 InputStream newIS = f.getJarFile().getInputStream(e)) {
+                return !differs(oldIS, newIS);
+            }
+        }
+
+        public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException {
+            String thisName = null;
+            Long crcL = Long.valueOf(entry.getCrc());
+            // check if this jar contains files with the passed in entry's crc
+            if (_crcToEntryMap.containsKey(crcL)) {
+                // get the Linked List with files with the crc
+                LinkedList<JarEntry> ll = _crcToEntryMap.get(crcL);
+                // go through the list and check for content match
+                ListIterator<JarEntry> li = ll.listIterator(0);
+                while (li.hasNext()) {
+                    JarEntry thisEntry = li.next();
+                    // check for content match
+                    try (InputStream oldIS = getJarFile().getInputStream(thisEntry);
+                         InputStream newIS = file.getJarFile().getInputStream(entry)) {
+                        if (!differs(oldIS, newIS)) {
+                            thisName = thisEntry.getName();
+                            return thisName;
+                        }
+                    }
+                }
+            }
+            return thisName;
+        }
+
+        private void index () throws IOException {
+            Enumeration<JarEntry> entries = _jar.entries();
+
+            _nameToEntryMap = new HashMap<>();
+            _crcToEntryMap = new HashMap<>();
+            _entries = new ArrayList<>();
+            if (_debug) {
+                System.out.println("indexing: " + _jar.getName());
+            }
+            if (entries != null) {
+                while (entries.hasMoreElements()) {
+                    JarEntry entry = entries.nextElement();
+                    long crc = entry.getCrc();
+                    Long crcL = Long.valueOf(crc);
+                    if (_debug) {
+                        System.out.println("\t" + entry.getName() + " CRC " + crc);
+                    }
+
+                    _nameToEntryMap.put(entry.getName(), entry);
+                    _entries.add(entry);
+
+                    // generate the CRC to entries map
+                    if (_crcToEntryMap.containsKey(crcL)) {
+                        // key exist, add the entry to the correcponding linked list
+                        LinkedList<JarEntry> ll = _crcToEntryMap.get(crcL);
+                        ll.add(entry);
+                        _crcToEntryMap.put(crcL, ll);
+
+                    } else {
+                        // create a new entry in the hashmap for the new key
+                        LinkedList<JarEntry> ll = new LinkedList<JarEntry>();
+                        ll.add(entry);
+                        _crcToEntryMap.put(crcL, ll);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            _jar.close();
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java
new file mode 100644 (file)
index 0000000..3b5db80
--- /dev/null
@@ -0,0 +1,24 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+/**
+ * Constants shared by {@link JarDiff} and {@link JarDiffPatcher}.
+ */
+public interface JarDiffCodes
+{
+    /** The name of the jardiff control file. */
+    String INDEX_NAME = "META-INF/INDEX.JD";
+
+    /** The version header used in the control file. */
+    String VERSION_HEADER = "version 1.0";
+
+    /** A jardiff command to remove an entry. */
+    String REMOVE_COMMAND = "remove";
+
+    /** A jardiff command to move an entry. */
+    String MOVE_COMMAND = "move";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java
new file mode 100644 (file)
index 0000000..b5a0a17
--- /dev/null
@@ -0,0 +1,292 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+import com.threerings.getdown.util.ProgressObserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Applies a jardiff patch to a jar file.
+ */
+public class JarDiffPatcher implements JarDiffCodes
+{
+    /**
+     * Patches the specified jar file using the supplied patch file and writing
+     * the new jar file to the supplied target.
+     *
+     * @param jarPath the path to the original jar file.
+     * @param diffPath the path to the jardiff patch file.
+     * @param target the output stream to which we will write the patched jar.
+     * @param observer an optional observer to be notified of patching progress.
+     *
+     * @throws IOException if any problem occurs during patching.
+     */
+    public void patchJar (String jarPath, String diffPath, File target, ProgressObserver observer)
+        throws IOException
+    {
+        File oldFile = new File(jarPath), diffFile = new File(diffPath);
+
+        try (JarFile oldJar = new JarFile(oldFile);
+             JarFile jarDiff = new JarFile(diffFile);
+             JarOutputStream jos = new JarOutputStream(new FileOutputStream(target))) {
+
+            Set<String> ignoreSet = new HashSet<>();
+            Map<String, String> renameMap = new HashMap<>();
+            determineNameMapping(jarDiff, ignoreSet, renameMap);
+
+            // get all keys in renameMap
+            String[] keys = renameMap.keySet().toArray(new String[renameMap.size()]);
+
+            // Files to implicit move
+            Set<String> oldjarNames  = new HashSet<>();
+            Enumeration<JarEntry> oldEntries = oldJar.entries();
+            if (oldEntries != null) {
+                while  (oldEntries.hasMoreElements()) {
+                    oldjarNames.add(oldEntries.nextElement().getName());
+                }
+            }
+
+            // size depends on the three parameters below, which is basically the
+            // counter for each loop that do the actual writes to the output file
+            // since oldjarNames.size() changes in the first two loop below, we
+            // need to adjust the size accordingly also when oldjarNames.size()
+            // changes
+            double size = oldjarNames.size() + keys.length + jarDiff.size();
+            double currentEntry = 0;
+
+            // Handle all remove commands
+            oldjarNames.removeAll(ignoreSet);
+            size -= ignoreSet.size();
+
+            // Add content from JARDiff
+            Enumeration<JarEntry> entries = jarDiff.entries();
+            if (entries != null) {
+                while (entries.hasMoreElements()) {
+                    JarEntry entry = entries.nextElement();
+                    if (!INDEX_NAME.equals(entry.getName())) {
+                        updateObserver(observer, currentEntry, size);
+                        currentEntry++;
+                        writeEntry(jos, entry, jarDiff);
+
+                        // Remove entry from oldjarNames since no implicit move is
+                        // needed
+                        boolean wasInOld = oldjarNames.remove(entry.getName());
+
+                        // Update progress counters. If it was in old, we do not
+                        // need an implicit move, so adjust total size.
+                        if (wasInOld) {
+                            size--;
+                        }
+
+                    } else {
+                        // no write is done, decrement size
+                        size--;
+                    }
+                }
+            }
+
+            // go through the renameMap and apply move for each entry
+            for (String newName : keys) {
+                // Apply move <oldName> <newName> command
+                String oldName = renameMap.get(newName);
+
+                // Get source JarEntry
+                JarEntry oldEntry = oldJar.getJarEntry(oldName);
+                if (oldEntry == null) {
+                    String moveCmd = MOVE_COMMAND + oldName + " " + newName;
+                    throw new IOException("error.badmove: " + moveCmd);
+                }
+
+                // Create dest JarEntry
+                JarEntry newEntry = new JarEntry(newName);
+                newEntry.setTime(oldEntry.getTime());
+                newEntry.setSize(oldEntry.getSize());
+                newEntry.setCompressedSize(oldEntry.getCompressedSize());
+                newEntry.setCrc(oldEntry.getCrc());
+                newEntry.setMethod(oldEntry.getMethod());
+                newEntry.setExtra(oldEntry.getExtra());
+                newEntry.setComment(oldEntry.getComment());
+
+                updateObserver(observer, currentEntry, size);
+                currentEntry++;
+
+                try (InputStream data = oldJar.getInputStream(oldEntry)) {
+                    writeEntry(jos, newEntry, data);
+                }
+
+                // Remove entry from oldjarNames since no implicit move is needed
+                boolean wasInOld = oldjarNames.remove(oldName);
+
+                // Update progress counters. If it was in old, we do not need an
+                // implicit move, so adjust total size.
+                if (wasInOld) {
+                    size--;
+                }
+            }
+
+            // implicit move
+            Iterator<String> iEntries = oldjarNames.iterator();
+            if (iEntries != null) {
+                while (iEntries.hasNext()) {
+                    String name = iEntries.next();
+                    JarEntry entry = oldJar.getJarEntry(name);
+                    if (entry == null) {
+                        // names originally retrieved from the JAR, so this should never happen
+                        throw new AssertionError("JAR entry not found: " + name);
+                    }
+                    updateObserver(observer, currentEntry, size);
+                    currentEntry++;
+                    writeEntry(jos, entry, oldJar);
+                }
+            }
+            updateObserver(observer, currentEntry, size);
+        }
+    }
+
+    protected void updateObserver (ProgressObserver observer, double currentSize, double size)
+    {
+        if (observer != null) {
+            observer.progress((int)(100*currentSize/size));
+        }
+    }
+
+    protected void determineNameMapping (
+        JarFile jarDiff, Set<String> ignoreSet, Map<String, String> renameMap)
+        throws IOException
+    {
+        InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME));
+        if (is == null) {
+            throw new IOException("error.noindex");
+        }
+
+        LineNumberReader indexReader =
+            new LineNumberReader(new InputStreamReader(is, UTF_8));
+        String line = indexReader.readLine();
+        if (line == null || !line.equals(VERSION_HEADER)) {
+            throw new IOException("jardiff.error.badheader: " + line);
+        }
+
+        while ((line = indexReader.readLine()) != null) {
+            if (line.startsWith(REMOVE_COMMAND)) {
+                List<String> sub = getSubpaths(
+                    line.substring(REMOVE_COMMAND.length()));
+
+                if (sub.size() != 1) {
+                    throw new IOException("error.badremove: " + line);
+                }
+                ignoreSet.add(sub.get(0));
+
+            } else if (line.startsWith(MOVE_COMMAND)) {
+                List<String> sub = getSubpaths(
+                    line.substring(MOVE_COMMAND.length()));
+                if (sub.size() != 2) {
+                    throw new IOException("error.badmove: " + line);
+                }
+
+                // target of move should be the key
+                if (renameMap.put(sub.get(1), sub.get(0)) != null) {
+                    // invalid move - should not move to same target twice
+                    throw new IOException("error.badmove: " + line);
+                }
+
+            } else if (line.length() > 0) {
+                throw new IOException("error.badcommand: " + line);
+            }
+        }
+    }
+
+    protected List<String> getSubpaths (String path)
+    {
+        int index = 0;
+        int length = path.length();
+        ArrayList<String> sub = new ArrayList<>();
+
+        while (index < length) {
+            while (index < length && Character.isWhitespace
+                   (path.charAt(index))) {
+                index++;
+            }
+            if (index < length) {
+                int start = index;
+                int last = start;
+                String subString = null;
+
+                while (index < length) {
+                    char aChar = path.charAt(index);
+                    if (aChar == '\\' && (index + 1) < length &&
+                        path.charAt(index + 1) == ' ') {
+
+                        if (subString == null) {
+                            subString = path.substring(last, index);
+                        } else {
+                            subString += path.substring(last, index);
+                        }
+                        last = ++index;
+                    } else if (Character.isWhitespace(aChar)) {
+                        break;
+                    }
+                    index++;
+                }
+                if (last != index) {
+                    if (subString == null) {
+                        subString = path.substring(last, index);
+                    } else {
+                        subString += path.substring(last, index);
+                    }
+                }
+                sub.add(subString);
+            }
+        }
+        return sub;
+    }
+
+    protected void writeEntry (JarOutputStream jos, JarEntry entry, JarFile file)
+        throws IOException
+    {
+        try (InputStream data = file.getInputStream(entry)) {
+            writeEntry(jos, entry, data);
+        }
+    }
+
+    protected void writeEntry (JarOutputStream jos, JarEntry entry, InputStream data)
+        throws IOException
+    {
+        jos.putNextEntry(new JarEntry(entry.getName()));
+
+        // Read the entry
+        int size = data.read(newBytes);
+        while (size != -1) {
+            jos.write(newBytes, 0, size);
+            size = data.read(newBytes);
+        }
+    }
+
+    protected static final int DEFAULT_READ_SIZE = 2048;
+
+    protected static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+    protected static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java
new file mode 100644 (file)
index 0000000..4ead59b
--- /dev/null
@@ -0,0 +1,205 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import com.threerings.getdown.util.FileUtil;
+import com.threerings.getdown.util.ProgressObserver;
+import com.threerings.getdown.util.StreamUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Applies a unified patch file to an application directory, providing
+ * percentage completion feedback along the way. <em>Note:</em> the
+ * patcher is not thread safe. Create a separate patcher instance for each
+ * patching action that is desired.
+ */
+public class Patcher
+{
+    /** A suffix appended to file names to indicate that a file should be newly created. */
+    public static final String CREATE = ".create";
+
+    /** A suffix appended to file names to indicate that a file should be patched. */
+    public static final String PATCH = ".patch";
+
+    /** A suffix appended to file names to indicate that a file should be deleted. */
+    public static final String DELETE = ".delete";
+
+    /**
+     * Applies the specified patch file to the application living in the
+     * specified application directory. The supplied observer, if
+     * non-null, will be notified of progress along the way.
+     *
+     * <p><em>Note:</em> this method runs on the calling thread, thus the
+     * caller may want to make use of a separate thread in conjunction
+     * with the patcher so that the user interface is not blocked for the
+     * duration of the patch.
+     */
+    public void patch (File appdir, File patch, ProgressObserver obs)
+        throws IOException
+    {
+        // save this information for later
+        _obs = obs;
+        _plength = patch.length();
+
+        try (JarFile file = new JarFile(patch)) {
+            Enumeration<JarEntry> entries = file.entries(); // old skool!
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                String path = entry.getName();
+                long elength = entry.getCompressedSize();
+
+                // depending on the suffix, we do The Right Thing (tm)
+                if (path.endsWith(CREATE)) {
+                    path = strip(path, CREATE);
+                    log.info("Creating " + path + "...");
+                    createFile(file, entry, new File(appdir, path));
+
+                } else if (path.endsWith(PATCH)) {
+                    path = strip(path, PATCH);
+                    log.info("Patching " + path + "...");
+                    patchFile(file, entry, appdir, path);
+
+                } else if (path.endsWith(DELETE)) {
+                    path = strip(path, DELETE);
+                    log.info("Removing " + path + "...");
+                    File target = new File(appdir, path);
+                    if (!FileUtil.deleteHarder(target)) {
+                        log.warning("Failure deleting '" + target + "'.");
+                    }
+
+                } else {
+                    log.warning("Skipping bogus patch file entry: " + path);
+                }
+
+                // note that we've completed this entry
+                _complete += elength;
+            }
+        }
+    }
+
+    protected String strip (String path, String suffix)
+    {
+        return path.substring(0, path.length() - suffix.length());
+    }
+
+    protected void createFile (JarFile file, ZipEntry entry, File target)
+    {
+        // create our copy buffer if necessary
+        if (_buffer == null) {
+            _buffer = new byte[COPY_BUFFER_SIZE];
+        }
+
+        // make sure the file's parent directory exists
+        File pdir = target.getParentFile();
+        if (!pdir.exists() && !pdir.mkdirs()) {
+            log.warning("Failed to create parent for '" + target + "'.");
+        }
+
+        try (InputStream in = file.getInputStream(entry);
+             FileOutputStream fout = new FileOutputStream(target)) {
+
+            int total = 0, read;
+            while ((read = in.read(_buffer)) != -1) {
+                total += read;
+                fout.write(_buffer, 0, read);
+                updateProgress(total);
+            }
+
+        } catch (IOException ioe) {
+            log.warning("Error creating '" + target + "': " + ioe);
+        }
+    }
+
+    protected void patchFile (JarFile file, ZipEntry entry,
+                              File appdir, String path)
+    {
+        File target = new File(appdir, path);
+        File patch = new File(appdir, entry.getName());
+        File otarget = new File(appdir, path + ".old");
+        JarDiffPatcher patcher = null;
+
+        // make sure no stale old target is lying around to mess us up
+        FileUtil.deleteHarder(otarget);
+
+        // pipe the contents of the patch into a file
+        try (InputStream in = file.getInputStream(entry);
+             FileOutputStream fout = new FileOutputStream(patch)) {
+
+            StreamUtil.copy(in, fout);
+            StreamUtil.close(fout);
+
+            // move the current version of the jar to .old
+            if (!FileUtil.renameTo(target, otarget)) {
+                log.warning("Failed to .oldify '" + target + "'.");
+                return;
+            }
+
+            // we'll need this to pass progress along to our observer
+            final long elength = entry.getCompressedSize();
+            ProgressObserver obs = new ProgressObserver() {
+                public void progress (int percent) {
+                    updateProgress((int)(percent * elength / 100));
+                }
+            };
+
+            // now apply the patch to create the new target file
+            patcher = new JarDiffPatcher();
+            patcher.patchJar(otarget.getPath(), patch.getPath(), target, obs);
+
+        } catch (IOException ioe) {
+            if (patcher == null) {
+                log.warning("Failed to write patch file '" + patch + "': " + ioe);
+            } else {
+                log.warning("Error patching '" + target + "': " + ioe);
+            }
+
+        } finally {
+            // clean up our temporary files
+            FileUtil.deleteHarder(patch);
+            FileUtil.deleteHarder(otarget);
+        }
+    }
+
+    protected void updateProgress (int progress)
+    {
+        if (_obs != null) {
+            _obs.progress((int)(100 * (_complete + progress) / _plength));
+        }
+    }
+
+    public static void main (String[] args)
+    {
+        if (args.length != 2) {
+            System.err.println("Usage: Patcher appdir patch_file");
+            System.exit(-1);
+        }
+
+        Patcher patcher = new Patcher();
+        try {
+            patcher.patch(new File(args[0]), new File(args[1]), null);
+        } catch (IOException ioe) {
+            System.err.println("Error: " + ioe.getMessage());
+            System.exit(-1);
+        }
+    }
+
+    protected ProgressObserver _obs;
+    protected long _complete, _plength;
+    protected byte[] _buffer;
+
+    protected static final int COPY_BUFFER_SIZE = 4096;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java
new file mode 100644 (file)
index 0000000..2a5db79
--- /dev/null
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.threerings.getdown.util;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ * Utilities for encoding and decoding the Base64 representation of
+ * binary data.  See RFCs <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
+ * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
+ */
+public class Base64 {
+    /**
+     * Default values for encoder/decoder flags.
+     */
+    public static final int DEFAULT = 0;
+
+    /**
+     * Encoder flag bit to omit the padding '=' characters at the end
+     * of the output (if any).
+     */
+    public static final int NO_PADDING = 1;
+
+    /**
+     * Encoder flag bit to omit all line terminators (i.e., the output
+     * will be on one long line).
+     */
+    public static final int NO_WRAP = 2;
+
+    /**
+     * Encoder flag bit to indicate lines should be terminated with a
+     * CRLF pair instead of just an LF.  Has no effect if {@code
+     * NO_WRAP} is specified as well.
+     */
+    public static final int CRLF = 4;
+
+    /**
+     * Encoder/decoder flag bit to indicate using the "URL and
+     * filename safe" variant of Base64 (see RFC 3548 section 4) where
+     * {@code -} and {@code _} are used in place of {@code +} and
+     * {@code /}.
+     */
+    public static final int URL_SAFE = 8;
+
+    /**
+     * Flag to pass to {@code Base64OutputStream} to indicate that it
+     * should not close the output stream it is wrapping when it
+     * itself is closed.
+     */
+    public static final int NO_CLOSE = 16;
+
+    //  --------------------------------------------------------
+    //  shared code
+    //  --------------------------------------------------------
+
+    /* package */ static abstract class Coder {
+        public byte[] output;
+        public int op;
+
+        /**
+         * Encode/decode another block of input data.  this.output is
+         * provided by the caller, and must be big enough to hold all
+         * the coded data.  On exit, this.opwill be set to the length
+         * of the coded data.
+         *
+         * @param finish true if this is the final call to process for
+         *        this object.  Will finalize the coder state and
+         *        include any final bytes in the output.
+         *
+         * @return true if the input so far is good; false if some
+         *         error has been detected in the input stream..
+         */
+        public abstract boolean process(byte[] input, int offset, int len, boolean finish);
+
+        /**
+         * @return the maximum number of bytes a call to process()
+         * could produce for the given number of input bytes.  This may
+         * be an overestimate.
+         */
+        public abstract int maxOutputSize(int len);
+    }
+
+    //  --------------------------------------------------------
+    //  decoding
+    //  --------------------------------------------------------
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param str    the input String to decode, which is converted to
+     *               bytes using ASCII
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(String str, int flags) {
+        return decode(str.getBytes(US_ASCII), flags);
+    }
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param input the input array to decode
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(byte[] input, int flags) {
+        return decode(input, 0, input.length, flags);
+    }
+
+    /**
+     * Decode the Base64-encoded data in input and return the data in
+     * a new byte array.
+     *
+     * <p>The padding '=' characters at the end are considered optional, but
+     * if any are present, there must be the correct number of them.
+     *
+     * @param input  the data to decode
+     * @param offset the position within the input array at which to start
+     * @param len    the number of bytes of input to decode
+     * @param flags  controls certain features of the decoded output.
+     *               Pass {@code DEFAULT} to decode standard Base64.
+     *
+     * @throws IllegalArgumentException if the input contains
+     * incorrect padding
+     */
+    public static byte[] decode(byte[] input, int offset, int len, int flags) {
+        // Allocate space for the most data the input could represent.
+        // (It could contain less if it contains whitespace, etc.)
+        Decoder decoder = new Decoder(flags, new byte[len*3/4]);
+
+        if (!decoder.process(input, offset, len, true)) {
+            throw new IllegalArgumentException("bad base-64");
+        }
+
+        // Maybe we got lucky and allocated exactly enough output space.
+        if (decoder.op == decoder.output.length) {
+            return decoder.output;
+        }
+
+        // Need to shorten the array, so allocate a new one of the
+        // right size and copy.
+        byte[] temp = new byte[decoder.op];
+        System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
+        return temp;
+    }
+
+    /* package */ static class Decoder extends Coder {
+        /**
+         * Lookup table for turning bytes into their position in the
+         * Base64 alphabet.
+         */
+        private static final int DECODE[] = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        };
+
+        /**
+         * Decode lookup table for the "web safe" variant (RFC 3548
+         * sec. 4) where - and _ replace + and /.
+         */
+        private static final int DECODE_WEBSAFE[] = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        };
+
+        /** Non-data values in the DECODE arrays. */
+        private static final int SKIP = -1;
+        private static final int EQUALS = -2;
+
+        /**
+         * States 0-3 are reading through the next input tuple.
+         * State 4 is having read one '=' and expecting exactly
+         * one more.
+         * State 5 is expecting no more data or padding characters
+         * in the input.
+         * State 6 is the error state; an error has been detected
+         * in the input and no future input can "fix" it.
+         */
+        private int state;   // state number (0 to 6)
+        private int value;
+
+        final private int[] alphabet;
+
+        public Decoder(int flags, byte[] output) {
+            this.output = output;
+
+            alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
+            state = 0;
+            value = 0;
+        }
+
+        /**
+         * @return an overestimate for the number of bytes {@code
+         * len} bytes could decode to.
+         */
+        public int maxOutputSize(int len) {
+            return len * 3/4 + 10;
+        }
+
+        /**
+         * Decode another block of input data.
+         *
+         * @return true if the state machine is still healthy.  false if
+         *         bad base-64 data has been detected in the input stream.
+         */
+        public boolean process(byte[] input, int offset, int len, boolean finish) {
+            if (this.state == 6) return false;
+
+            int p = offset;
+            len += offset;
+
+            // Using local variables makes the decoder about 12%
+            // faster than if we manipulate the member variables in
+            // the loop.  (Even alphabet makes a measurable
+            // difference, which is somewhat surprising to me since
+            // the member variable is final.)
+            int state = this.state;
+            int value = this.value;
+            int op = 0;
+            final byte[] output = this.output;
+            final int[] alphabet = this.alphabet;
+
+            while (p < len) {
+                // Try the fast path:  we're starting a new tuple and the
+                // next four bytes of the input stream are all data
+                // bytes.  This corresponds to going through states
+                // 0-1-2-3-0.  We expect to use this method for most of
+                // the data.
+                //
+                // If any of the next four bytes of input are non-data
+                // (whitespace, etc.), value will end up negative.  (All
+                // the non-data values in decode are small negative
+                // numbers, so shifting any of them up and or'ing them
+                // together will result in a value with its top bit set.)
+                //
+                // You can remove this whole block and the output should
+                // be the same, just slower.
+                if (state == 0) {
+                    while (p+4 <= len &&
+                           (value = ((alphabet[input[p] & 0xff] << 18) |
+                                     (alphabet[input[p+1] & 0xff] << 12) |
+                                     (alphabet[input[p+2] & 0xff] << 6) |
+                                     (alphabet[input[p+3] & 0xff]))) >= 0) {
+                        output[op+2] = (byte) value;
+                        output[op+1] = (byte) (value >> 8);
+                        output[op] = (byte) (value >> 16);
+                        op += 3;
+                        p += 4;
+                    }
+                    if (p >= len) break;
+                }
+
+                // The fast path isn't available -- either we've read a
+                // partial tuple, or the next four input bytes aren't all
+                // data, or whatever.  Fall back to the slower state
+                // machine implementation.
+
+                int d = alphabet[input[p++] & 0xff];
+
+                switch (state) {
+                case 0:
+                    if (d >= 0) {
+                        value = d;
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 1:
+                    if (d >= 0) {
+                        value = (value << 6) | d;
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 2:
+                    if (d >= 0) {
+                        value = (value << 6) | d;
+                        ++state;
+                    } else if (d == EQUALS) {
+                        // Emit the last (partial) output tuple;
+                        // expect exactly one more padding character.
+                        output[op++] = (byte) (value >> 4);
+                        state = 4;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 3:
+                    if (d >= 0) {
+                        // Emit the output triple and return to state 0.
+                        value = (value << 6) | d;
+                        output[op+2] = (byte) value;
+                        output[op+1] = (byte) (value >> 8);
+                        output[op] = (byte) (value >> 16);
+                        op += 3;
+                        state = 0;
+                    } else if (d == EQUALS) {
+                        // Emit the last (partial) output tuple;
+                        // expect no further data or padding characters.
+                        output[op+1] = (byte) (value >> 2);
+                        output[op] = (byte) (value >> 10);
+                        op += 2;
+                        state = 5;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 4:
+                    if (d == EQUALS) {
+                        ++state;
+                    } else if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+
+                case 5:
+                    if (d != SKIP) {
+                        this.state = 6;
+                        return false;
+                    }
+                    break;
+                }
+            }
+
+            if (!finish) {
+                // We're out of input, but a future call could provide
+                // more.
+                this.state = state;
+                this.value = value;
+                this.op = op;
+                return true;
+            }
+
+            // Done reading input.  Now figure out where we are left in
+            // the state machine and finish up.
+
+            switch (state) {
+            case 0:
+                // Output length is a multiple of three.  Fine.
+                break;
+            case 1:
+                // Read one extra input byte, which isn't enough to
+                // make another output byte.  Illegal.
+                this.state = 6;
+                return false;
+            case 2:
+                // Read two extra input bytes, enough to emit 1 more
+                // output byte.  Fine.
+                output[op++] = (byte) (value >> 4);
+                break;
+            case 3:
+                // Read three extra input bytes, enough to emit 2 more
+                // output bytes.  Fine.
+                output[op++] = (byte) (value >> 10);
+                output[op++] = (byte) (value >> 2);
+                break;
+            case 4:
+                // Read one padding '=' when we expected 2.  Illegal.
+                this.state = 6;
+                return false;
+            case 5:
+                // Read all the padding '='s we expected and no more.
+                // Fine.
+                break;
+            }
+
+            this.state = state;
+            this.op = op;
+            return true;
+        }
+    }
+
+    //  --------------------------------------------------------
+    //  encoding
+    //  --------------------------------------------------------
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * String with the result.
+     *
+     * @param input  the data to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static String encodeToString(byte[] input, int flags) {
+        return new String(encode(input, flags), US_ASCII);
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * String with the result.
+     *
+     * @param input  the data to encode
+     * @param offset the position within the input array at which to
+     *               start
+     * @param len    the number of bytes of input to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static String encodeToString(byte[] input, int offset, int len, int flags) {
+        return new String(encode(input, offset, len, flags), US_ASCII);
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * byte[] with the result.
+     *
+     * @param input  the data to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static byte[] encode(byte[] input, int flags) {
+        return encode(input, 0, input.length, flags);
+    }
+
+    /**
+     * Base64-encode the given data and return a newly allocated
+     * byte[] with the result.
+     *
+     * @param input  the data to encode
+     * @param offset the position within the input array at which to
+     *               start
+     * @param len    the number of bytes of input to encode
+     * @param flags  controls certain features of the encoded output.
+     *               Passing {@code DEFAULT} results in output that
+     *               adheres to RFC 2045.
+     */
+    public static byte[] encode(byte[] input, int offset, int len, int flags) {
+        Encoder encoder = new Encoder(flags, null);
+
+        // Compute the exact length of the array we will produce.
+        int output_len = len / 3 * 4;
+
+        // Account for the tail of the data and the padding bytes, if any.
+        if (encoder.do_padding) {
+            if (len % 3 > 0) {
+                output_len += 4;
+            }
+        } else {
+            switch (len % 3) {
+                case 0: break;
+                case 1: output_len += 2; break;
+                case 2: output_len += 3; break;
+            }
+        }
+
+        // Account for the newlines, if any.
+        if (encoder.do_newline && len > 0) {
+            output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
+                (encoder.do_cr ? 2 : 1);
+        }
+
+        encoder.output = new byte[output_len];
+        encoder.process(input, offset, len, true);
+
+        assert encoder.op == output_len;
+
+        return encoder.output;
+    }
+
+    /* package */ static class Encoder extends Coder {
+        /**
+         * Emit a new line every this many output tuples.  Corresponds to
+         * a 76-character line length (the maximum allowable according to
+         * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
+         */
+        public static final int LINE_GROUPS = 19;
+
+        /**
+         * Lookup table for turning Base64 alphabet positions (6 bits)
+         * into output bytes.
+         */
+        private static final byte ENCODE[] = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+        };
+
+        /**
+         * Lookup table for turning Base64 alphabet positions (6 bits)
+         * into output bytes.
+         */
+        private static final byte ENCODE_WEBSAFE[] = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+        };
+
+        final private byte[] tail;
+        /* package */ int tailLen;
+        private int count;
+
+        final public boolean do_padding;
+        final public boolean do_newline;
+        final public boolean do_cr;
+        final private byte[] alphabet;
+
+        public Encoder(int flags, byte[] output) {
+            this.output = output;
+
+            do_padding = (flags & NO_PADDING) == 0;
+            do_newline = (flags & NO_WRAP) == 0;
+            do_cr = (flags & CRLF) != 0;
+            alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+            tail = new byte[2];
+            tailLen = 0;
+
+            count = do_newline ? LINE_GROUPS : -1;
+        }
+
+        /**
+         * @return an overestimate for the number of bytes {@code
+         * len} bytes could encode to.
+         */
+        public int maxOutputSize(int len) {
+            return len * 8/5 + 10;
+        }
+
+        public boolean process(byte[] input, int offset, int len, boolean finish) {
+            // Using local variables makes the encoder about 9% faster.
+            final byte[] alphabet = this.alphabet;
+            final byte[] output = this.output;
+            int op = 0;
+            int count = this.count;
+
+            int p = offset;
+            len += offset;
+            int v = -1;
+
+            // First we need to concatenate the tail of the previous call
+            // with any input bytes available now and see if we can empty
+            // the tail.
+
+            switch (tailLen) {
+                case 0:
+                    // There was no tail.
+                    break;
+
+                case 1:
+                    if (p+2 <= len) {
+                        // A 1-byte tail with at least 2 bytes of
+                        // input available now.
+                        v = ((tail[0] & 0xff) << 16) |
+                            ((input[p++] & 0xff) << 8) |
+                            (input[p++] & 0xff);
+                        tailLen = 0;
+                    };
+                    break;
+
+                case 2:
+                    if (p+1 <= len) {
+                        // A 2-byte tail with at least 1 byte of input.
+                        v = ((tail[0] & 0xff) << 16) |
+                            ((tail[1] & 0xff) << 8) |
+                            (input[p++] & 0xff);
+                        tailLen = 0;
+                    }
+                    break;
+            }
+
+            if (v != -1) {
+                output[op++] = alphabet[(v >> 18) & 0x3f];
+                output[op++] = alphabet[(v >> 12) & 0x3f];
+                output[op++] = alphabet[(v >> 6) & 0x3f];
+                output[op++] = alphabet[v & 0x3f];
+                if (--count == 0) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                    count = LINE_GROUPS;
+                }
+            }
+
+            // At this point either there is no tail, or there are fewer
+            // than 3 bytes of input available.
+
+            // The main loop, turning 3 input bytes into 4 output bytes on
+            // each iteration.
+            while (p+3 <= len) {
+                v = ((input[p] & 0xff) << 16) |
+                    ((input[p+1] & 0xff) << 8) |
+                    (input[p+2] & 0xff);
+                output[op] = alphabet[(v >> 18) & 0x3f];
+                output[op+1] = alphabet[(v >> 12) & 0x3f];
+                output[op+2] = alphabet[(v >> 6) & 0x3f];
+                output[op+3] = alphabet[v & 0x3f];
+                p += 3;
+                op += 4;
+                if (--count == 0) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                    count = LINE_GROUPS;
+                }
+            }
+
+            if (finish) {
+                // Finish up the tail of the input.  Note that we need to
+                // consume any bytes in tail before any bytes
+                // remaining in input; there should be at most two bytes
+                // total.
+
+                if (p-tailLen == len-1) {
+                    int t = 0;
+                    v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
+                    tailLen -= t;
+                    output[op++] = alphabet[(v >> 6) & 0x3f];
+                    output[op++] = alphabet[v & 0x3f];
+                    if (do_padding) {
+                        output[op++] = '=';
+                        output[op++] = '=';
+                    }
+                    if (do_newline) {
+                        if (do_cr) output[op++] = '\r';
+                        output[op++] = '\n';
+                    }
+                } else if (p-tailLen == len-2) {
+                    int t = 0;
+                    v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
+                        (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
+                    tailLen -= t;
+                    output[op++] = alphabet[(v >> 12) & 0x3f];
+                    output[op++] = alphabet[(v >> 6) & 0x3f];
+                    output[op++] = alphabet[v & 0x3f];
+                    if (do_padding) {
+                        output[op++] = '=';
+                    }
+                    if (do_newline) {
+                        if (do_cr) output[op++] = '\r';
+                        output[op++] = '\n';
+                    }
+                } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+                    if (do_cr) output[op++] = '\r';
+                    output[op++] = '\n';
+                }
+
+                assert tailLen == 0;
+                assert p == len;
+            } else {
+                // Save the leftovers in tail to be consumed on the next
+                // call to encodeInternal.
+
+                if (p == len-1) {
+                    tail[tailLen++] = input[p];
+                } else if (p == len-2) {
+                    tail[tailLen++] = input[p];
+                    tail[tailLen++] = input[p+1];
+                }
+            }
+
+            this.op = op;
+            this.count = count;
+
+            return true;
+        }
+    }
+
+    private Base64() { }   // don't instantiate
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java
new file mode 100644 (file)
index 0000000..047cead
--- /dev/null
@@ -0,0 +1,27 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+/**
+ * Utilities for handling ARGB colors.
+ */
+public class Color
+{
+    public final static int CLEAR = 0x00000000;
+    public final static int WHITE = 0xFFFFFFFF;
+    public final static int BLACK = 0xFF000000;
+
+    public static float brightness (int argb) {
+        // TODO: we're ignoring alpha here...
+        int red = (argb >> 16) & 0xFF;
+        int green = (argb >> 8) & 0xFF;
+        int blue = (argb >> 0) & 0xFF;
+        int max = Math.max(Math.max(red, green), blue);
+        return ((float) max) / 255.0f;
+    }
+
+    private Color () {}
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java
new file mode 100644 (file)
index 0000000..e20bae2
--- /dev/null
@@ -0,0 +1,477 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Handles parsing and runtime access for Getdown's config files (mainly {@code getdown.txt}).
+ * These files contain zero or more mappings for a particular string key. Config values can be
+ * fetched as single strings, lists of strings, or parsed into primitives or compound data types
+ * like colors and rectangles.
+ */
+public class Config
+{
+    /** Empty configuration. */
+    public static final Config EMPTY = new Config(new HashMap<String, Object>());
+
+    /** Options that control the {@link #parsePairs} function. */
+    public static class ParseOpts {
+        // these should be tweaked as desired by the caller
+        public boolean biasToKey = false;
+        public boolean strictComments = false;
+
+        // these are filled in by parseConfig
+        public String osname = null;
+        public String osarch = null;
+    }
+
+    /**
+     * Creates a parse configuration, filling in the platform filters (or not) depending on the
+     * value of {@code checkPlatform}.
+     */
+    public static ParseOpts createOpts (boolean checkPlatform) {
+        ParseOpts opts = new ParseOpts();
+        if (checkPlatform) {
+            opts.osname = StringUtil.deNull(System.getProperty("os.name")).toLowerCase(Locale.ROOT);
+            opts.osarch = StringUtil.deNull(System.getProperty("os.arch")).toLowerCase(Locale.ROOT);
+        }
+        return opts;
+    }
+
+    /**
+     * Parses a configuration file containing key/value pairs. The file must be in the UTF-8
+     * encoding.
+     *
+     * @param opts options that influence the parsing. See {@link #createOpts}.
+     *
+     * @return a list of <code>String[]</code> instances containing the key/value pairs in the
+     * order they were parsed from the file.
+     */
+    public static List<String[]> parsePairs (File source, ParseOpts opts)
+        throws IOException
+    {
+        // annoyingly FileReader does not allow encoding to be specified (uses platform default)
+        try (FileInputStream fis = new FileInputStream(source);
+             InputStreamReader input = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
+            return parsePairs(input, opts);
+        }
+    }
+
+    /**
+     * See {@link #parsePairs(File,ParseOpts)}.
+     */
+    public static List<String[]> parsePairs (Reader source, ParseOpts opts) throws IOException
+    {
+        List<String[]> pairs = new ArrayList<>();
+        for (String line : FileUtil.readLines(source)) {
+            // nix comments
+            int cidx = line.indexOf("#");
+            if (opts.strictComments ? cidx == 0 : cidx != -1) {
+                line = line.substring(0, cidx);
+            }
+
+            // trim whitespace and skip blank lines
+            line = line.trim();
+            if (StringUtil.isBlank(line)) {
+                continue;
+            }
+
+            // parse our key/value pair
+            String[] pair = new String[2];
+            // if we're biasing toward key, put all the extra = in the key rather than the value
+            int eidx = opts.biasToKey ? line.lastIndexOf("=") : line.indexOf("=");
+            if (eidx != -1) {
+                pair[0] = line.substring(0, eidx).trim();
+                pair[1] = line.substring(eidx+1).trim();
+            } else {
+                pair[0] = line;
+                pair[1] = "";
+            }
+
+            // if the pair has an os qualifier, we need to process it
+            if (pair[1].startsWith("[")) {
+                int qidx = pair[1].indexOf("]");
+                if (qidx == -1) {
+                    log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]);
+                    continue; // omit the pair entirely
+                }
+                // if we're checking qualifiers and the os doesn't match this qualifier, skip it
+                String quals = pair[1].substring(1, qidx);
+                if (opts.osname != null && !checkQualifiers(quals, opts.osname, opts.osarch)) {
+                    log.debug("Skipping", "quals", quals,
+                              "osname", opts.osname, "osarch", opts.osarch,
+                              "key", pair[0], "value", pair[1]);
+                    continue;
+                }
+                // otherwise filter out the qualifier text
+                pair[1] = pair[1].substring(qidx+1).trim();
+            }
+
+            pairs.add(pair);
+        }
+
+        return pairs;
+    }
+
+    /**
+     * Takes a comma-separated String of four integers and returns a rectangle using those ints as
+     * the its x, y, width, and height.
+     */
+    public static Rectangle parseRect (String name, String value)
+    {
+        if (!StringUtil.isBlank(value)) {
+            int[] v = StringUtil.parseIntArray(value);
+            if (v != null && v.length == 4) {
+                return new Rectangle(v[0], v[1], v[2], v[3]);
+            }
+            log.warning("Ignoring invalid rect '" + name + "' config '" + value + "'.");
+        }
+        return null;
+    }
+
+    /**
+     * Parses the given hex color value (e.g. FFCC99) and returns an {@code Integer} with that
+     * value. If the given value is null or not a valid hexadecimal number, this will return null.
+     */
+    public static Integer parseColor (String hexValue)
+    {
+        if (!StringUtil.isBlank(hexValue)) {
+            try {
+                // if no alpha channel is specified, use 255 (full alpha)
+                int alpha = hexValue.length() > 6 ? 0 : 0xFF000000;
+                return Integer.parseInt(hexValue, 16) | alpha;
+            } catch (NumberFormatException e) {
+                log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Parses a configuration file containing key/value pairs. The file must be in the UTF-8
+     * encoding.
+     *
+     * @return a map from keys to values, where a value will be an array of strings if more than
+     * one key/value pair in the config file was associated with the same key.
+     */
+    public static Config parseConfig (File source, ParseOpts opts)
+        throws IOException
+    {
+        Map<String, Object> data = new HashMap<>();
+
+        // I thought that we could use HashMap<String, String[]> and put new String[] {pair[1]} for
+        // the null case, but it mysteriously dies on launch, so leaving it as HashMap<String,
+        // Object> for now
+        for (String[] pair : parsePairs(source, opts)) {
+            Object value = data.get(pair[0]);
+            if (value == null) {
+                data.put(pair[0], pair[1]);
+            } else if (value instanceof String) {
+                data.put(pair[0], new String[] { (String)value, pair[1] });
+            } else if (value instanceof String[]) {
+                String[] values = (String[])value;
+                String[] nvalues = new String[values.length+1];
+                System.arraycopy(values, 0, nvalues, 0, values.length);
+                nvalues[values.length] = pair[1];
+                data.put(pair[0], nvalues);
+            }
+        }
+
+        // special magic for the getdown.txt config: if the parsed data contains 'strict_comments =
+        // true' then we reparse the file with strict comments (i.e. # is only assumed to start a
+        // comment in column 0)
+        if (!opts.strictComments && Boolean.parseBoolean((String)data.get("strict_comments"))) {
+            opts.strictComments = true;
+            return parseConfig(source, opts);
+        }
+
+        return new Config(data);
+    }
+
+    public Config (Map<String,  Object> data) {
+        _data = data;
+    }
+
+    /**
+     * Returns whether {@code name} has a value in this config.
+     */
+    public boolean hasValue (String name) {
+        return _data.containsKey(name);
+    }
+
+    /**
+     * Returns the raw-value for {@code name}. This may be a {@code String}, {@code String[]}, or
+     * {@code null}.
+     */
+    public Object getRaw (String name) {
+        return _data.get(name);
+    }
+
+    /**
+     * Returns the specified config value as a string, or {@code null}.
+     */
+    public String getString (String name) {
+        return (String)_data.get(name);
+    }
+
+    /**
+     * Returns the specified config value as a string, or {@code def}.
+     */
+    public String getString (String name, String def) {
+        String value = (String)_data.get(name);
+        return value == null ? def : value;
+    }
+
+    /**
+     * Returns the specified config value as a boolean.
+     */
+    public boolean getBoolean (String name) {
+        return Boolean.parseBoolean(getString(name));
+    }
+
+    /**
+     * Massages a single string into an array and leaves existing array values as is. Simplifies
+     * access to parameters that are expected to be arrays.
+     */
+    public String[] getMultiValue (String name)
+    {
+        Object value = _data.get(name);
+        if (value == null) {
+          return new String[] {};
+        }
+        if (value instanceof String) {
+            return new String[] { (String)value };
+        } else {
+            return (String[])value;
+        }
+    }
+
+    /** Used to parse rectangle specifications from the config file. */
+    public Rectangle getRect (String name, Rectangle def)
+    {
+        String value = getString(name);
+        Rectangle rect = parseRect(name, value);
+        return (rect == null) ? def : rect;
+    }
+
+    /**
+     * Parses and returns the config value for {@code name} as an int. If no value is provided,
+     * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is
+     * returned.
+     */
+    public int getInt (String name, int def) {
+        String value = getString(name);
+        try {
+            return value == null ? def : Integer.parseInt(value);
+        } catch (Exception e) {
+            log.warning("Ignoring invalid int '" + name + "' config '" + value + "',");
+            return def;
+        }
+    }
+
+    /**
+     * Parses and returns the config value for {@code name} as a long. If no value is provided,
+     * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is
+     * returned.
+     */
+    public long getLong (String name, long def) {
+        String value = getString(name);
+        try {
+            return value == null ? def : Long.parseLong(value);
+        } catch (Exception e) {
+            log.warning("Ignoring invalid long '" + name + "' config '" + value + "',");
+            return def;
+        }
+    }
+
+    /** Used to parse color specifications from the config file. */
+    public int getColor (String name, int def)
+    {
+        String value = getString(name);
+        Integer color = parseColor(value);
+        return (color == null) ? def : color;
+    }
+
+    /** Parses a list of strings from the config file. */
+    public String[] getList (String name)
+    {
+        String value = getString(name);
+        return (value == null) ? new String[0] : StringUtil.parseStringArray(value);
+    }
+
+    /**
+     * Parses a URL from the config file, checking first for a localized version.
+     */
+    public String getUrl (String name, String def)
+    {
+        String value = getString(name + "." + Locale.getDefault().getLanguage());
+        if (StringUtil.isBlank(value)) {
+            value = getString(name);
+        }
+        if (StringUtil.isBlank(value)) {
+            value = def;
+        }
+        if (!StringUtil.isBlank(value)) {
+            try {
+                HostWhitelist.verify(new URL(value));
+            } catch (MalformedURLException e) {
+                log.warning("Invalid URL.", "url", value, e);
+                value = null;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following
+     * form:
+     * <pre>
+     * id = os[-arch]
+     * ids = id | id,ids
+     * quals = !id | ids
+     * </pre>
+     * Examples: [linux-amd64,linux-x86_64], [windows], [mac os x], [!windows]. Negative qualifiers
+     * must appear alone, they cannot be used with other qualifiers (positive or negative).
+     */
+    protected static boolean checkQualifiers (String quals, String osname, String osarch)
+    {
+        if (quals.startsWith("!")) {
+            if (quals.indexOf(",") != -1) { // sanity check
+                log.warning("Multiple qualifiers cannot be used when one of the qualifiers " +
+                            "is negative", "quals", quals);
+                return false;
+            }
+            return !checkQualifier(quals.substring(1), osname, osarch);
+        }
+        for (String qual : quals.split(",")) {
+            if (checkQualifier(qual, osname, osarch)) {
+                return true; // if we have a positive match, we can immediately return true
+            }
+        }
+        return false; // we had no positive matches, so return false
+    }
+
+    /** A helper function for {@link #checkQualifiers}. */
+    protected static boolean checkQualifier (String qual, String osname, String osarch)
+    {
+        String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-");
+        String os = bits[0], arch = (bits.length > 1) ? bits[1] : "";
+        return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1);
+    }
+    
+    public void mergeConfig(Config newValues, boolean merge) {
+      
+      for (Map.Entry<String, Object> entry : newValues.getData().entrySet()) {
+        
+        String key = entry.getKey();
+        Object nvalue = entry.getValue();
+
+        String mkey = key.indexOf('.') > -1 ? key.substring(key.indexOf('.') + 1) : key;
+        if (merge && allowedMergeKeys.contains(mkey)) {
+          
+          // merge multi values
+          
+          Object value = _data.get(key);
+          
+          if (value == null) {
+            _data.put(key, nvalue);
+          } else if (value instanceof String) {
+            if (nvalue instanceof String) {
+              
+              // value is String, nvalue is String
+              _data.put(key, new String[] { (String)value, (String)nvalue });
+              
+            } else if (nvalue instanceof String[]) {
+              
+              // value is String, nvalue is String[]
+              String[] nvalues = (String[])nvalue;
+              String[] newvalues = new String[nvalues.length+1];
+              newvalues[0] = (String)value;
+              System.arraycopy(nvalues, 0, newvalues, 1, nvalues.length);
+              _data.put(key, newvalues);
+              
+            }
+          } else if (value instanceof String[]) {
+            if (nvalue instanceof String) {
+              
+              // value is String[], nvalue is String
+              String[] values = (String[])value;
+              String[] newvalues = new String[values.length+1];
+              System.arraycopy(values, 0, newvalues, 0, values.length);
+              newvalues[values.length] = (String)nvalue;
+              _data.put(key, newvalues);
+              
+            } else if (nvalue instanceof String[]) {
+              
+              // value is String[], nvalue is String[]
+              String[] values = (String[])value;
+              String[] nvalues = (String[])nvalue;
+              String[] newvalues = new String[values.length + nvalues.length];
+              System.arraycopy(values, 0, newvalues, 0, values.length);
+              System.arraycopy(nvalues, 0, newvalues, values.length, newvalues.length);
+              _data.put(key, newvalues);
+              
+            }
+          }
+          
+        } else if (allowedReplaceKeys.contains(mkey)){
+          
+          // replace value
+          
+          _data.put(key, nvalue);
+        } else {
+          log.warning("Not merging key '"+key+"' into config");
+        }
+
+      }
+      
+    }
+    
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+      for (Map.Entry<String, Object> entry : getData().entrySet()) {
+        String key = entry.getKey();
+        Object val = entry.getValue();
+        sb.append(key);
+        sb.append("=");
+        if (val instanceof String) {
+          sb.append((String)val);
+        } else if (val instanceof String[]) {
+          sb.append(Arrays.toString((String[])val));
+        } else {
+          sb.append("Value not String or String[]");
+        }
+        sb.append("\n");
+      }
+      return sb.toString();
+    }
+    
+    public Map<String, Object> getData() {
+      return _data;
+    }
+
+    private final Map<String, Object> _data;
+    public static final List<String> allowedReplaceKeys = Arrays.asList("appbase","apparg","jvmarg"); // these are the ones we might use
+    public static final List<String> allowedMergeKeys = Arrays.asList("apparg","jvmarg"); // these are the ones we might use
+    //private final List<String> allowedMergeKeys = Arrays.asList("apparg","jvmarg","resource","code","java_location"); // (not exhaustive list here)
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java
new file mode 100644 (file)
index 0000000..21b0569
--- /dev/null
@@ -0,0 +1,73 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+
+import com.threerings.getdown.data.SysProps;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class ConnectionUtil
+{
+    /**
+     * Opens a connection to a URL, setting the authentication header if user info is present.
+     * @param proxy the proxy via which to perform HTTP connections.
+     * @param url the URL to which to open a connection.
+     * @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the
+     * connection. If {@code 0} is supplied, the connection timeout specified via system properties
+     * will be used instead.
+     * @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from
+     * the connection. If {@code 0} is supplied, the read timeout specified via system properties
+     * will be used instead.
+     */
+    public static URLConnection open (Proxy proxy, URL url, int connectTimeout, int readTimeout)
+        throws IOException
+    {
+        URLConnection conn = url.openConnection(proxy);
+
+        // configure a connect timeout, if requested
+        int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
+        if (ctimeout > 0) {
+            conn.setConnectTimeout(ctimeout * 1000);
+        }
+
+        // configure a read timeout, if requested
+        int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout();
+        if (rtimeout > 0) {
+            conn.setReadTimeout(rtimeout * 1000);
+        }
+
+        // If URL has a username:password@ before hostname, use HTTP basic auth
+        String userInfo = url.getUserInfo();
+        if (userInfo != null) {
+            // Remove any percent-encoding in the username/password
+            userInfo = URLDecoder.decode(userInfo, "UTF-8");
+            // Now base64 encode the auth info and make it a single line
+            String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT).
+                replaceAll("\\n","").replaceAll("\\r", "");
+            conn.setRequestProperty("Authorization", "Basic " + encoded);
+        }
+
+        return conn;
+    }
+
+    /**
+     * Opens a connection to a http or https URL, setting the authentication header if user info is
+     * present. Throws a class cast exception if the connection returned is not the right type. See
+     * {@link #open} for parameter documentation.
+     */
+    public static HttpURLConnection openHttp (
+        Proxy proxy, URL url, int connectTimeout, int readTimeout) throws IOException
+    {
+        return (HttpURLConnection)open(proxy, url, connectTimeout, readTimeout);
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java
new file mode 100644 (file)
index 0000000..67d0330
--- /dev/null
@@ -0,0 +1,316 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+
+import com.threerings.getdown.util.StreamUtil;
+import com.threerings.getdown.Log;
+import static com.threerings.getdown.Log.log;
+
+/**
+ * File related utilities.
+ */
+public class FileUtil
+{
+    /**
+     * Gets the specified source file to the specified destination file by hook or crook. Windows
+     * has all sorts of problems which we work around in this method.
+     *
+     * @return true if we managed to get the job done, false otherwise.
+     */
+    public static boolean renameTo (File source, File dest)
+    {
+        // if we're on a civilized operating system we may be able to simple rename it
+        if (source.renameTo(dest)) {
+            return true;
+        }
+
+        // fall back to trying to rename the old file out of the way, rename the new file into
+        // place and then delete the old file
+        if (dest.exists()) {
+            File temp = new File(dest.getPath() + "_old");
+            if (temp.exists() && !deleteHarder(temp)) {
+                log.warning("Failed to delete old intermediate file " + temp + ".");
+                // the subsequent code will probably fail
+            }
+            if (dest.renameTo(temp) && source.renameTo(dest)) {
+                if (!deleteHarder(temp)) {
+                    log.warning("Failed to delete intermediate file " + temp + ".");
+                }
+                return true;
+            }
+        }
+
+        // as a last resort, try copying the old data over the new
+        try {
+            copy(source, dest);
+        } catch (IOException ioe) {
+            log.warning("Failed to copy " + source + " to " + dest + ": " + ioe);
+            return false;
+        }
+
+        if (!deleteHarder(source)) {
+            log.warning("Failed to delete " + source + " after brute force copy to " + dest + ".");
+        }
+        return true;
+    }
+
+    /**
+     * "Tries harder" to delete {@code file} than just calling {@code delete} on it. Presently this
+     * just means "try a second time if the first time fails, and if that fails then try to delete
+     * when the virtual machine terminates." On Windows Vista, sometimes deletes fail but then
+     * succeed if you just try again. Given that delete failure is a rare occurrence, we can
+     * implement this hacky workaround without any negative consequences for normal behavior.
+     */
+    public static boolean deleteHarder (File file) {
+        // if at first you don't succeed... try, try again
+        boolean deleted = (file.delete() || file.delete());
+        if (!deleted) {
+            file.deleteOnExit();
+        }
+        return deleted;
+    }
+
+    /**
+     * Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}.
+     * Note that some children may still be deleted even if {@code false} is returned. Also, since
+     * {@link #deleteHarder} is used, the {@code file} could be deleted once the jvm exits even if
+     * {@code false} is returned.
+     *
+     * @param file file to delete.
+     * @return true iff {@code file} was successfully deleted.
+     */
+    public static boolean deleteDirHarder (File file) {
+        if (file.isDirectory()) {
+            for (File child : file.listFiles()) {
+                deleteDirHarder(child);
+            }
+        }
+        return deleteHarder(file);
+    }
+
+    /**
+     * Reads the contents of the supplied input stream into a list of lines. Closes the reader on
+     * successful or failed completion.
+     */
+    public static List<String> readLines (Reader in)
+        throws IOException
+    {
+        List<String> lines = new ArrayList<>();
+        try (BufferedReader bin = new BufferedReader(in)) {
+            for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {}
+        }
+        return lines;
+    }
+
+    /**
+     * Unpacks the specified jar file into the specified target directory.
+     * @param cleanExistingDirs if true, all files in all directories contained in {@code jar} will
+     * be deleted prior to unpacking the jar.
+     */
+    public static void unpackJar (JarFile jar, File target, boolean cleanExistingDirs)
+        throws IOException
+    {
+        if (cleanExistingDirs) {
+            Enumeration<?> entries = jar.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = (JarEntry)entries.nextElement();
+                if (entry.isDirectory()) {
+                    File efile = new File(target, entry.getName());
+                    if (efile.exists()) {
+                        for (File f : efile.listFiles()) {
+                            if (!f.isDirectory())
+                            f.delete();
+                        }
+                    }
+                }
+            }
+        }
+
+        Enumeration<?> entries = jar.entries();
+        while (entries.hasMoreElements()) {
+            JarEntry entry = (JarEntry)entries.nextElement();
+            File efile = new File(target, entry.getName());
+
+            // if we're unpacking a normal jar file, it will have special path
+            // entries that allow us to create our directories first
+            if (entry.isDirectory()) {
+                if (!efile.exists() && !efile.mkdir()) {
+                    log.warning("Failed to create jar entry path", "jar", jar, "entry", entry);
+                }
+                continue;
+            }
+
+            // but some do not, so we want to ensure that our directories exist
+            // prior to getting down and funky
+            File parent = new File(efile.getParent());
+            if (!parent.exists() && !parent.mkdirs()) {
+                log.warning("Failed to create jar entry parent", "jar", jar, "parent", parent);
+                continue;
+            }
+
+            try (BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(efile));
+                InputStream jin = jar.getInputStream(entry)) {
+                StreamUtil.copy(jin, fout);
+            } catch (Exception e) {
+                throw new IOException(
+                    Log.format("Failure unpacking", "jar", jar, "entry", efile), e);
+            }
+        }
+    }
+
+    /**
+     * Unpacks the specified tgz file into the specified target directory.
+     * @param cleanExistingDirs if true, all files in all directories contained in {@code tgz} will
+     * be deleted prior to unpacking the tgz.
+     */
+    public static void unpackTgz (TarArchiveInputStream tgz, File target, boolean cleanExistingDirs)
+        throws IOException
+    {
+               TarArchiveEntry entry;
+               while ((entry = tgz.getNextTarEntry()) != null) {
+            // sanitize the entry name
+                       String entryName = entry.getName();
+                       if (entryName.startsWith(File.separator))
+                       {
+                               entryName = entryName.substring(File.separator.length());
+                       }
+            File efile = new File(target, entryName);
+
+            // if we're unpacking a normal tgz file, it will have special path
+            // entries that allow us to create our directories first
+            if (entry.isDirectory()) {
+               
+                               if (cleanExistingDirs) {
+                    if (efile.exists()) {
+                        for (File f : efile.listFiles()) {
+                            if (!f.isDirectory())
+                            f.delete();
+                        }
+                    }
+                               }
+                               
+                if (!efile.exists() && !efile.mkdir()) {
+                    log.warning("Failed to create tgz entry path", "tgz", tgz, "entry", entry);
+                }
+                continue;
+            }
+
+            // but some do not, so we want to ensure that our directories exist
+            // prior to getting down and funky
+            File parent = new File(efile.getParent());
+            if (!parent.exists() && !parent.mkdirs()) {
+                log.warning("Failed to create tgz entry parent", "tgz", tgz, "parent", parent);
+                continue;
+            }
+
+            if (entry.isLink())
+            {
+               System.out.println("Creating hard link "+efile.getName()+" -> "+entry.getLinkName());
+               Files.createLink(efile.toPath(), Paths.get(entry.getLinkName()));
+               continue;
+            }
+
+            if (entry.isSymbolicLink())
+            {
+               System.out.println("Creating symbolic link "+efile.getName()+" -> "+entry.getLinkName());
+               Files.createSymbolicLink(efile.toPath(), Paths.get(entry.getLinkName()));
+               continue;
+            }
+            
+            try (BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(efile));
+                InputStream tin = tgz;) {
+                StreamUtil.copy(tin, fout);
+            } catch (Exception e) {
+                throw new IOException(
+                    Log.format("Failure unpacking", "tgz", tgz, "entry", efile), e);
+            }
+        }
+    }
+
+    /**
+     * Unpacks a pack200 packed jar file from {@code packedJar} into {@code target}. If {@code
+     * packedJar} has a {@code .gz} extension, it will be gunzipped first.
+     */
+    public static void unpackPacked200Jar (File packedJar, File target) throws IOException
+    {
+        try (InputStream packJarIn = new FileInputStream(packedJar);
+             JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(target))) {
+            boolean gz = (packedJar.getName().endsWith(".gz") ||
+                          packedJar.getName().endsWith(".gz_new"));
+            try (InputStream packJarIn2 = (gz ? new GZIPInputStream(packJarIn) : packJarIn)) {
+                Pack200.Unpacker unpacker = Pack200.newUnpacker();
+                unpacker.unpack(packJarIn2, jarOut);
+            }
+        }
+    }
+
+    /**
+     * Copies the given {@code source} file to the given {@code target}.
+     */
+    public static void copy (File source, File target) throws IOException {
+        try (FileInputStream in = new FileInputStream(source);
+             FileOutputStream out = new FileOutputStream(target)) {
+            StreamUtil.copy(in, out);
+        }
+    }
+
+    /**
+     * Marks {@code file} as executable, if it exists. Catches and logs any errors that occur.
+     */
+    public static void makeExecutable (File file) {
+        try {
+            if (file.exists()) {
+                if (!file.setExecutable(true, false)) {
+                    log.warning("Failed to mark as executable", "file", file);
+                }
+            }
+        } catch (Exception e) {
+            log.warning("Failed to mark as executable", "file", file, "error", e);
+        }
+    }
+
+    /**
+     * Used by {@link #walkTree}.
+     */
+    public interface Visitor
+    {
+        void visit (File file);
+    }
+
+    /**
+     * Walks all files in {@code root}, calling {@code visitor} on each file in the tree.
+     */
+    public static void walkTree (File root, Visitor visitor)
+    {
+        File[] children = root.listFiles();
+        if (children == null) return;
+        Deque<File> stack = new ArrayDeque<>(Arrays.asList(children));
+        while (!stack.isEmpty()) {
+            File currentFile = stack.pop();
+            if (currentFile.exists()) {
+                visitor.visit(currentFile);
+                File[] currentChildren = currentFile.listFiles();
+                if (currentChildren != null) {
+                    for (File file : currentChildren) {
+                        stack.push(file);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java
new file mode 100644 (file)
index 0000000..f2f7ef3
--- /dev/null
@@ -0,0 +1,63 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import com.threerings.getdown.data.Build;
+
+/**
+ * Optional support for compiling a URL host whitelist into the Getdown JAR.
+ * Useful if you're on the paranoid end of the security spectrum.
+ *
+ * @see Build#hostWhitelist()
+ */
+public final class HostWhitelist
+{
+    /**
+     * Verifies that the specified URL should be accessible, per the built-in host whitelist.
+     * See {@link Build#hostWhitelist()} and {@link #verify(List,URL)}.
+     */
+    public static URL verify (URL url) throws MalformedURLException
+    {
+        
+      
+        return verify(Build.hostWhitelist(), url);
+    }
+
+    /**
+     * Verifies that the specified URL should be accessible, per the supplied host whitelist.
+     * If the URL should not be accessible, this method throws a {@link MalformedURLException}.
+     * If the URL should be accessible, this method simply returns the {@link URL} passed in.
+     */
+    public static URL verify (List<String> hosts, URL url) throws MalformedURLException
+    {
+        if (url == null || hosts.isEmpty()) {
+            // either there is no URL to validate or no whitelist was configured
+            return url;
+        }
+
+        String urlHost = url.getHost();
+        String protocol = url.getProtocol();
+        
+        if (ALLOW_LOCATOR_FILE_PROTOCOL && protocol.equals("file") && urlHost.equals("")) {
+          return url;
+        }
+        
+        for (String host : hosts) {
+            String regex = host.replace(".", "\\.").replace("*", ".*");
+            if (urlHost.matches(regex)) {
+                return url;
+            }
+        }
+
+        throw new MalformedURLException(
+            "The host for the specified URL (" + url + ") is not in the host whitelist: " + hosts);
+    }
+    private static boolean ALLOW_LOCATOR_FILE_PROTOCOL = true;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java
new file mode 100644 (file)
index 0000000..f2cd573
--- /dev/null
@@ -0,0 +1,251 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Useful routines for launching Java applications from within other Java
+ * applications.
+ */
+public class LaunchUtil
+{
+    /** The directory into which a local VM installation should be unpacked. */
+    public static final String LOCAL_JAVA_DIR = "jre";
+
+    /**
+     * Writes a <code>version.txt</code> file into the specified application directory and
+     * attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly
+     * specified version and relaunch the application.
+     *
+     * @param appdir the directory in which the application is installed.
+     * @param getdownJarName the name of the getdown jar file in the application directory. This is
+     * probably <code>getdown-pro.jar</code> or <code>getdown-retro-pro.jar</code> if you are using
+     * the results of the standard build.
+     * @param newVersion the new version to which Getdown will update when it is executed.
+     *
+     * @return true if the relaunch succeeded, false if we were unable to relaunch due to being on
+     * Windows 9x where we cannot launch subprocesses without waiting around for them to exit,
+     * reading their stdout and stderr all the while. If true is returned, the application may exit
+     * after making this call as it will be upgraded and restarted. If false is returned, the
+     * application should tell the user that they must restart the application manually.
+     *
+     * @exception IOException thrown if we were unable to create the <code>version.txt</code> file
+     * in the supplied application directory. If the version.txt file cannot be created, restarting
+     * Getdown will not cause the application to be upgraded, so the application will have to
+     * resort to telling the user that it is in a bad way.
+     */
+    public static boolean updateVersionAndRelaunch (
+            File appdir, String getdownJarName, String newVersion)
+        throws IOException
+    {
+        // create the file that instructs Getdown to upgrade
+        File vfile = new File(appdir, "version.txt");
+        try (PrintStream ps = new PrintStream(new FileOutputStream(vfile))) {
+            ps.println(newVersion);
+        }
+
+        // make sure that we can find our getdown.jar file and can safely launch children
+        File pro = new File(appdir, getdownJarName);
+        if (mustMonitorChildren() || !pro.exists()) {
+            return false;
+        }
+
+        // do the deed
+        String[] args = new String[] {
+            getJVMPath(appdir), "-jar", pro.toString(), appdir.getPath()
+        };
+        log.info("Running " + StringUtil.join(args, "\n  "));
+        try {
+            Runtime.getRuntime().exec(args, null);
+            return true;
+        } catch (IOException ioe) {
+            log.warning("Failed to run getdown", ioe);
+            return false;
+        }
+    }
+
+    /**
+     * Reconstructs the path to the JVM used to launch this process.
+     */
+    public static String getJVMPath (File appdir)
+    {
+        return getJVMPath(appdir, false);
+    }
+
+    /**
+     * Reconstructs the path to the JVM used to launch this process.
+     *
+     * @param windebug if true we will use java.exe instead of javaw.exe on Windows.
+     */
+    public static String getJVMPath (File appdir, boolean windebug)
+    {
+        // first look in our application directory for an installed VM
+        String vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR).getAbsolutePath(), windebug);
+        if (vmpath == null && isMacOS()) {
+                       vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR + "/Contents/Home").getAbsolutePath(), windebug);
+        }
+
+        // then fall back to the VM in which we're already running
+        if (vmpath == null) {
+            vmpath = checkJVMPath(System.getProperty("java.home"), windebug);
+        }
+
+        // then throw up our hands and hope for the best
+        if (vmpath == null) {
+            log.warning("Unable to find java [appdir=" + appdir +
+                        ", java.home=" + System.getProperty("java.home") + "]!");
+            vmpath = "java";
+        }
+
+        // Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched
+        // from /usr/bin/java, and not if launched by directly referring to <java.home>/bin/java,
+        // even though the former is a symlink to the latter! To work around this, see if the
+        // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead.
+        if (isMacOS()) {
+            try {
+                File localVM = new File("/usr/bin/java").getCanonicalFile();
+                if (localVM.equals(new File(vmpath).getCanonicalFile())) {
+                    vmpath = "/usr/bin/java";
+                }
+            } catch (IOException ioe) {
+                log.warning("Failed to check Mac OS canonical VM path.", ioe);
+            }
+        }
+
+        return vmpath;
+    }
+
+    /**
+     * Upgrades Getdown by moving an installation managed copy of the Getdown jar file over the
+     * non-managed copy (which would be used to run Getdown itself).
+     *
+     * <p> If the upgrade fails for a variety of reasons, warnings are logged but no other actions
+     * are taken. There's not much else one can do other than try again next time around.
+     */
+    public static void upgradeGetdown (File oldgd, File curgd, File newgd)
+    {
+        // we assume getdown's jar file size changes with every upgrade, this is not guaranteed,
+        // but in reality it will, and it allows us to avoid pointlessly upgrading getdown every
+        // time the client is updated which is unnecessarily flirting with danger
+        if (!newgd.exists() || newgd.length() == curgd.length()) {
+            return;
+        }
+
+        log.info("Updating Getdown with " + newgd + "...");
+
+        // clear out any old getdown
+        if (oldgd.exists()) {
+            FileUtil.deleteHarder(oldgd);
+        }
+
+        // now try updating using renames
+        if (!curgd.exists() || curgd.renameTo(oldgd)) {
+            if (newgd.renameTo(curgd)) {
+                FileUtil.deleteHarder(oldgd); // yay!
+                try {
+                    // copy the moved file back to getdown-dop-new.jar so that we don't end up
+                    // downloading another copy next time
+                    FileUtil.copy(curgd, newgd);
+                } catch (IOException e) {
+                    log.warning("Error copying updated Getdown back: " + e);
+                }
+                return;
+            }
+
+            log.warning("Unable to renameTo(" + oldgd + ").");
+            // try to unfuck ourselves
+            if (!oldgd.renameTo(curgd)) {
+                log.warning("Oh God, why dost thee scorn me so.");
+            }
+        }
+
+        // that didn't work, let's try copying it
+        log.info("Attempting to upgrade by copying over " + curgd + "...");
+        try {
+            FileUtil.copy(newgd, curgd);
+        } catch (IOException ioe) {
+            log.warning("Mayday! Brute force copy method also failed.", ioe);
+        }
+    }
+
+    /**
+     * Returns true if, on this operating system, we have to stick around and read the stderr from
+     * our children processes to prevent them from filling their output buffers and hanging.
+     */
+    public static boolean mustMonitorChildren ()
+    {
+        String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
+        return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1);
+    }
+
+    /**
+     * Returns true if we're running in a JVM that identifies its operating system as Windows.
+     */
+    public static final boolean isWindows () { return _isWindows; }
+
+    /**
+     * Returns true if we're running in a JVM that identifies its operating system as MacOS.
+     */
+    public static final boolean isMacOS () { return _isMacOS; }
+
+    /**
+     * Returns true if we're running in a JVM that identifies its operating system as Linux.
+     */
+    public static final boolean isLinux () { return _isLinux; }
+
+    /**
+     * Checks whether a Java Virtual Machine can be located in the supplied path.
+     */
+    protected static String checkJVMPath (String vmhome, boolean windebug)
+    {
+        String vmbase = vmhome + File.separator + "bin" + File.separator;
+        String vmpath = vmbase + "java";
+        if (new File(vmpath).exists()) {
+            return vmpath;
+        }
+
+        if (!windebug) {
+            vmpath = vmbase + "javaw.exe";
+            if (new File(vmpath).exists()) {
+                return vmpath;
+            }
+        }
+
+        vmpath = vmbase + "java.exe";
+        if (new File(vmpath).exists()) {
+            return vmpath;
+        }
+
+        return null;
+    }
+
+    /** Flag indicating that we're on Windows; initialized when this class is first loaded. */
+    protected static boolean _isWindows;
+    /** Flag indicating that we're on MacOS; initialized when this class is first loaded. */
+    protected static boolean _isMacOS;
+    /** Flag indicating that we're on Linux; initialized when this class is first loaded. */
+    protected static boolean _isLinux;
+
+    static {
+        try {
+            String osname = System.getProperty("os.name");
+            osname = (osname == null) ? "" : osname;
+            _isWindows = (osname.indexOf("Windows") != -1);
+            _isMacOS = (osname.indexOf("Mac OS") != -1 ||
+                        osname.indexOf("MacOS") != -1);
+            _isLinux = (osname.indexOf("Linux") != -1);
+        } catch (Exception e) {
+            // can't grab system properties; we'll just pretend we're not on any of these OSes
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java
new file mode 100644 (file)
index 0000000..28dbdcf
--- /dev/null
@@ -0,0 +1,144 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+public class MessageUtil {
+
+    /**
+     * Returns whether or not the provided string is tainted. See {@link #taint}. Null strings
+     * are considered untainted.
+     */
+    public static boolean isTainted (String text)
+    {
+        return text != null && text.startsWith(TAINT_CHAR);
+    }
+
+    /**
+     * Call this to "taint" any string that has been entered by an entity outside the application
+     * so that the translation code knows not to attempt to translate this string when doing
+     * recursive translations.
+     */
+    public static String taint (Object text)
+    {
+        return TAINT_CHAR + text;
+    }
+
+    /**
+     * Removes the tainting character added to a string by {@link #taint}. If the provided string
+     * is not tainted, this silently returns the originally provided string.
+     */
+    public static String untaint (String text)
+    {
+        return isTainted(text) ? text.substring(TAINT_CHAR.length()) : text;
+    }
+
+    /**
+     * Composes a message key with an array of arguments. The message can subsequently be
+     * decomposed and translated without prior knowledge of how many arguments were provided.
+     */
+    public static String compose (String key, Object... args)
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append(key);
+        buf.append('|');
+        for (int i = 0; i < args.length; i++) {
+            if (i > 0) {
+                buf.append('|');
+            }
+            // escape the string while adding to the buffer
+            String arg = (args[i] == null) ? "" : String.valueOf(args[i]);
+            int alength = arg.length();
+            for (int p = 0; p < alength; p++) {
+                char ch = arg.charAt(p);
+                if (ch == '|') {
+                    buf.append("\\!");
+                } else if (ch == '\\') {
+                    buf.append("\\\\");
+                } else {
+                    buf.append(ch);
+                }
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Compose a message with String args. This is just a convenience so callers do not have to
+     * cast their String[] to an Object[].
+     */
+    public static String compose (String key, String... args)
+    {
+        return compose(key, (Object[]) args);
+    }
+
+    /**
+     * A convenience method for calling {@link #compose(String,Object[])} with an array of
+     * arguments that will be automatically tainted (see {@link #taint}).
+     */
+    public static String tcompose (String key, Object... args)
+    {
+        int acount = args.length;
+        String[] targs = new String[acount];
+        for (int ii = 0; ii < acount; ii++) {
+            targs[ii] = taint(args[ii]);
+        }
+        return compose(key, (Object[]) targs);
+    }
+
+    /**
+     * A convenience method for calling {@link #compose(String,String[])} with an array of argument
+     * that will be automatically tainted.
+     */
+    public static String tcompose (String key, String... args)
+    {
+        for (int ii = 0, nn = args.length; ii < nn; ii++) {
+            args[ii] = taint(args[ii]);
+        }
+        return compose(key, args);
+    }
+
+    /**
+     * Used to escape single quotes so that they are not interpreted by <code>MessageFormat</code>.
+     * As we assume all single quotes are to be escaped, we cannot use the characters
+     * <code>{</code> and <code>}</code> in our translation strings, but this is a small price to
+     * pay to have to differentiate between messages that will and won't eventually be parsed by a
+     * <code>MessageFormat</code> instance.
+     */
+    public static String escape (String message)
+    {
+        return message.replace("'", "''");
+    }
+
+    /**
+     * Unescapes characters that are escaped in a call to compose.
+     */
+    public static String unescape (String value)
+    {
+        int bsidx = value.indexOf('\\');
+        if (bsidx == -1) {
+            return value;
+        }
+
+        StringBuilder buf = new StringBuilder();
+        int vlength = value.length();
+        for (int ii = 0; ii < vlength; ii++) {
+            char ch = value.charAt(ii);
+            if (ch != '\\' || ii == vlength-1) {
+                buf.append(ch);
+            } else {
+                // look at the next character
+                ch = value.charAt(++ii);
+                buf.append((ch == '!') ? '|' : ch);
+            }
+        }
+
+        return buf.toString();
+    }
+
+    /** Text prefixed by this character will be considered tainted when doing recursive
+     * translations and won't be translated. */
+    protected static final String TAINT_CHAR = "~";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java
new file mode 100644 (file)
index 0000000..d74b011
--- /dev/null
@@ -0,0 +1,50 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+/**
+ * Accumulates the progress from a number of (potentially parallel) elements into a single smoothly
+ * progressing progress.
+ */
+public class ProgressAggregator
+{
+    public ProgressAggregator (ProgressObserver target, long[] sizes) {
+        _target = target;
+        _sizes = sizes;
+        _progress = new int[sizes.length];
+    }
+
+    public ProgressObserver startElement (final int index) {
+        return new ProgressObserver() {
+            public void progress (int percent) {
+                _progress[index] = percent;
+                updateAggProgress();
+            }
+        };
+    }
+
+    protected void updateAggProgress () {
+        long totalSize = 0L, currentSize = 0L;
+        synchronized (this) {
+            for (int ii = 0, ll = _sizes.length; ii < ll; ii++) {
+                long size = _sizes[ii];
+                totalSize += size;
+                currentSize += (int)((size * _progress[ii])/100.0);
+            }
+        }
+        _target.progress((int)(100.0*currentSize / totalSize));
+    }
+
+    protected static long sum (long[] sizes) {
+        long totalSize = 0L;
+        for (long size : sizes) totalSize += size;
+        return totalSize;
+    }
+
+    protected ProgressObserver _target;
+    protected long[] _sizes;
+    protected int[] _progress;
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java
new file mode 100644 (file)
index 0000000..ad4c560
--- /dev/null
@@ -0,0 +1,18 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+/**
+ * Used to communicate progress.
+ */
+public interface ProgressObserver
+{
+    /**
+     * Informs the observer that we have completed the specified
+     * percentage of the process.
+     */
+    public void progress (int percent);
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java
new file mode 100644 (file)
index 0000000..3671d7d
--- /dev/null
@@ -0,0 +1,40 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+/**
+ * An immutable rectangle.
+ */
+public class Rectangle
+{
+    public final int x;
+    public final int y;
+    public final int width;
+    public final int height;
+
+    public Rectangle (int x, int y, int width, int height)
+    {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
+
+    public Rectangle union (Rectangle other) {
+        int x1 = Math.min(x, other.x);
+        int x2 = Math.max(x + width, other.x + other.width);
+        int y1 = Math.min(y, other.y);
+        int y2 = Math.max(y + height, other.y + other.height);
+        return new Rectangle(x1, y1, x2 - x1, y2 - y1);
+    }
+
+    /** {@inheritDoc} */
+    public String toString ()
+    {
+        return getClass().getName() + "[x=" + x + ", y=" + y +
+            ", width=" + width + ", height=" + height + "]";
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java
new file mode 100644 (file)
index 0000000..373cfff
--- /dev/null
@@ -0,0 +1,96 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import static com.threerings.getdown.Log.log;
+
+public class StreamUtil {
+    /**
+     * Convenient close for a stream. Use in a finally clause and love life.
+     */
+    public static void close (InputStream in)
+    {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ioe) {
+                log.warning("Error closing input stream", "stream", in, "cause", ioe);
+            }
+        }
+    }
+
+    /**
+     * Convenient close for a stream. Use in a finally clause and love life.
+     */
+    public static void close (OutputStream out)
+    {
+        if (out != null) {
+            try {
+                out.close();
+            } catch (IOException ioe) {
+                log.warning("Error closing output stream", "stream", out, "cause", ioe);
+            }
+        }
+    }
+
+    /**
+     * Convenient close for a Reader. Use in a finally clause and love life.
+     */
+    public static void close (Reader in)
+    {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ioe) {
+                log.warning("Error closing reader", "reader", in, "cause", ioe);
+            }
+        }
+    }
+
+    /**
+     * Convenient close for a Writer. Use in a finally clause and love life.
+     */
+    public static void close (Writer out)
+    {
+        if (out != null) {
+            try {
+                out.close();
+            } catch (IOException ioe) {
+                log.warning("Error closing writer", "writer", out, "cause", ioe);
+            }
+        }
+    }
+
+    /**
+     * Copies the contents of the supplied input stream to the supplied output stream.
+     */
+    public static <T extends OutputStream> T copy (InputStream in, T out)
+        throws IOException
+    {
+        byte[] buffer = new byte[4096];
+        for (int read = 0; (read = in.read(buffer)) > 0; ) {
+            out.write(buffer, 0, read);
+        }
+        return out;
+    }
+
+    /**
+     * Reads the contents of the supplied stream into a byte array.
+     */
+    public static byte[] toByteArray (InputStream stream)
+        throws IOException
+    {
+        return copy(stream, new ByteArrayOutputStream()).toByteArray();
+    }
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java
new file mode 100644 (file)
index 0000000..03d3c9c
--- /dev/null
@@ -0,0 +1,206 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.util.StringTokenizer;
+
+public class StringUtil {
+
+    /**
+     * @return true if the specified string could be a valid URL (contains no illegal characters)
+     */
+    public static boolean couldBeValidUrl (String url)
+    {
+        return url.matches("[A-Za-z0-9\\-\\._~:/\\?#\\[\\]@!$&'\\(\\)\\*\\+,;=%]+");
+    }
+
+    /**
+     * @return true if the string is null or consists only of whitespace, false otherwise.
+     */
+    public static boolean isBlank (String value)
+    {
+        for (int ii = 0, ll = (value == null) ? 0 : value.length(); ii < ll; ii++) {
+            if (!Character.isWhitespace(value.charAt(ii))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Parses an array of integers from it's string representation. The array should be represented
+     * as a bare list of numbers separated by commas, for example:
+     *
+     * <pre>25, 17, 21, 99</pre>
+     *
+     * Any inability to parse the int array will result in the function returning null.
+     */
+    public static int[] parseIntArray (String source)
+    {
+        StringTokenizer tok = new StringTokenizer(source, ",");
+        int[] vals = new int[tok.countTokens()];
+        for (int i = 0; tok.hasMoreTokens(); i++) {
+            try {
+                // trim the whitespace from the token
+                vals[i] = Integer.parseInt(tok.nextToken().trim());
+            } catch (NumberFormatException nfe) {
+                return null;
+            }
+        }
+        return vals;
+    }
+
+    /**
+     * Parses an array of strings from a single string. The array should be represented as a bare
+     * list of strings separated by commas, for example:
+     *
+     * <pre>mary, had, a, little, lamb, and, an, escaped, comma,,</pre>
+     *
+     * If a comma is desired in one of the strings, it should be escaped by putting two commas in a
+     * row. Any inability to parse the string array will result in the function returning null.
+     */
+    public static String[] parseStringArray (String source)
+    {
+        return parseStringArray(source, false);
+    }
+
+    /**
+     * Like {@link #parseStringArray(String)} but can be instructed to invoke {@link String#intern}
+     * on the strings being parsed into the array.
+     */
+    public static String[] parseStringArray (String source, boolean intern)
+    {
+        int tcount = 0, tpos = -1, tstart = 0;
+
+        // empty strings result in zero length arrays
+        if (source.length() == 0) {
+            return new String[0];
+        }
+
+        // sort out escaped commas
+        source = source.replace(",,", "%COMMA%");
+
+        // count up the number of tokens
+        while ((tpos = source.indexOf(",", tpos+1)) != -1) {
+            tcount++;
+        }
+
+        String[] tokens = new String[tcount+1];
+        tpos = -1; tcount = 0;
+
+        // do the split
+        while ((tpos = source.indexOf(",", tpos+1)) != -1) {
+            tokens[tcount] = source.substring(tstart, tpos);
+            tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
+            if (intern) {
+                tokens[tcount] = tokens[tcount].intern();
+            }
+            tstart = tpos+1;
+            tcount++;
+        }
+
+        // grab the last token
+        tokens[tcount] = source.substring(tstart);
+        tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
+
+        return tokens;
+    }
+
+    /**
+     * @return the supplied string if it is non-null, "" if it is null.
+     */
+    public static String deNull (String value)
+    {
+        return (value == null) ? "" : value;
+    }
+
+    /**
+     * Generates a string from the supplied bytes that is the HEX encoded representation of those
+     * bytes.  Returns the empty string for a <code>null</code> or empty byte array.
+     *
+     * @param bytes the bytes for which we want a string representation.
+     * @param count the number of bytes to stop at (which will be coerced into being {@code <=} the
+     * length of the array).
+     */
+    public static String hexlate (byte[] bytes, int count)
+    {
+        if (bytes == null) {
+            return "";
+        }
+
+        count = Math.min(count, bytes.length);
+        char[] chars = new char[count*2];
+
+        for (int i = 0; i < count; i++) {
+            int val = bytes[i];
+            if (val < 0) {
+                val += 256;
+            }
+            chars[2*i] = XLATE.charAt(val/16);
+            chars[2*i+1] = XLATE.charAt(val%16);
+        }
+
+        return new String(chars);
+    }
+
+    /**
+     * Generates a string from the supplied bytes that is the HEX encoded representation of those
+     * bytes.
+     */
+    public static String hexlate (byte[] bytes)
+    {
+        return (bytes == null) ? "" : hexlate(bytes, bytes.length);
+    }
+
+    /**
+     * Joins an array of strings (or objects which will be converted to strings) into a single
+     * string separated by commas.
+     */
+    public static String join (Object[] values)
+    {
+        return join(values, false);
+    }
+
+    /**
+     * Joins an array of strings into a single string, separated by commas, and optionally escaping
+     * commas that occur in the individual string values such that a subsequent call to {@link
+     * #parseStringArray} would recreate the string array properly. Any elements in the values
+     * array that are null will be treated as an empty string.
+     */
+    public static String join (Object[] values, boolean escape)
+    {
+        return join(values, ", ", escape);
+    }
+
+    /**
+     * Joins the supplied array of strings into a single string separated by the supplied
+     * separator.
+     */
+    public static String join (Object[] values, String separator)
+    {
+        return join(values, separator, false);
+    }
+
+    /**
+     * Helper function for the various <code>join</code> methods.
+     */
+    protected static String join (Object[] values, String separator, boolean escape)
+    {
+        StringBuilder buf = new StringBuilder();
+        int vlength = values.length;
+        for (int i = 0; i < vlength; i++) {
+            if (i > 0) {
+                buf.append(separator);
+            }
+            String value = (values[i] == null) ? "" : values[i].toString();
+            buf.append((escape) ? value.replace(",", ",,") : value);
+        }
+        return buf.toString();
+    }
+
+    /** Used by {@link #hexlate} and {@link #unhexlate}. */
+    protected static final String XLATE = "0123456789abcdef";
+}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java
new file mode 100644 (file)
index 0000000..49e4e6e
--- /dev/null
@@ -0,0 +1,114 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.threerings.getdown.data.SysProps;
+import static com.threerings.getdown.Log.log;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Version related utilities.
+ */
+public class VersionUtil
+{
+    /**
+     * Reads a version number from a file.
+     */
+    public static long readVersion (File vfile)
+    {
+        long fileVersion = -1;
+        try (BufferedReader bin =
+             new BufferedReader(new InputStreamReader(new FileInputStream(vfile), UTF_8))) {
+            String vstr = bin.readLine();
+            if (!StringUtil.isBlank(vstr)) {
+                fileVersion = Long.parseLong(vstr);
+            }
+        } catch (Exception e) {
+            log.info("Unable to read version file: " + e.getMessage());
+        }
+
+        return fileVersion;
+    }
+
+    /**
+     * Writes a version number to a file.
+     */
+    public static void writeVersion (File vfile, long version) throws IOException
+    {
+        try (PrintStream out = new PrintStream(new FileOutputStream(vfile))) {
+            out.println(version);
+        } catch (Exception e) {
+            log.warning("Unable to write version file: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Parses {@code versStr} using {@code versRegex} into a (long) integer version number.
+     * @see SysProps#parseJavaVersion
+     */
+    public static long parseJavaVersion (String versRegex, String versStr)
+    {
+        Matcher m = Pattern.compile(versRegex).matcher(versStr);
+        if (!m.matches()) return 0L;
+
+        long vers = 0L;
+        for (int ii = 1; ii <= m.groupCount(); ii++) {
+            String valstr = m.group(ii);
+            int value = (valstr == null) ? 0 : parseInt(valstr);
+            vers *= 100;
+            vers += value;
+        }
+        return vers;
+    }
+
+    /**
+     * Reads and parses the version from the {@code release} file bundled with a JVM.
+     */
+    public static long readReleaseVersion (File relfile, String versRegex)
+    {
+        try (BufferedReader in =
+             new BufferedReader(new InputStreamReader(new FileInputStream(relfile), UTF_8))) {
+            String line = null, relvers = null;
+            while ((line = in.readLine()) != null) {
+                if (line.startsWith("JAVA_VERSION=")) {
+                    relvers = line.substring("JAVA_VERSION=".length()).replace('"', ' ').trim();
+                }
+            }
+
+            if (relvers == null) {
+                log.warning("No JAVA_VERSION line in 'release' file", "file", relfile);
+                return 0L;
+            }
+            return parseJavaVersion(versRegex, relvers);
+
+        } catch (Exception e) {
+            log.warning("Failed to read version from 'release' file", "file", relfile, e);
+            return 0L;
+        }
+    }
+
+    private static int parseInt (String str) {
+        int value = 0;
+        for (int ii = 0, ll = str.length(); ii < ll; ii++) {
+            char c = str.charAt(ii);
+            if (c >= '0' && c <= '9') {
+                value *= 10;
+                value += (c - '0');
+            }
+        }
+        return value;
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java
new file mode 100644 (file)
index 0000000..d5a3937
--- /dev/null
@@ -0,0 +1,71 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Validates that cache garbage is collected and deleted correctly.
+ */
+public class GarbageCollectorTest
+{
+    @Before public void setupFiles () throws IOException
+    {
+        _cachedFile = _folder.newFile("abc123.jar");
+        _lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
+    }
+
+    @Test public void shouldDeleteCacheEntryIfRetentionPeriodIsReached ()
+    {
+        gcNow();
+        assertFalse(_cachedFile.exists());
+        assertFalse(_lastAccessedFile.exists());
+    }
+
+    @Test public void shouldDeleteCacheFolderIfFolderIsEmpty ()
+    {
+        gcNow();
+        assertFalse(_folder.getRoot().exists());
+    }
+
+    private void gcNow() {
+        GarbageCollector.collect(_folder.getRoot(), -1);
+    }
+
+    @Test public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached ()
+    {
+        GarbageCollector.collect(_folder.getRoot(), TimeUnit.DAYS.toMillis(1));
+        assertTrue(_cachedFile.exists());
+        assertTrue(_lastAccessedFile.exists());
+    }
+
+    @Test public void shouldDeleteCachedFileIfLastAccessedFileIsMissing ()
+    {
+        assumeTrue(_lastAccessedFile.delete());
+        gcNow();
+        assertFalse(_cachedFile.exists());
+    }
+
+    @Test public void shouldDeleteLastAccessedFileIfCachedFileIsMissing ()
+    {
+        assumeTrue(_cachedFile.delete());
+        gcNow();
+        assertFalse(_lastAccessedFile.exists());
+    }
+
+    @Rule public TemporaryFolder _folder = new TemporaryFolder();
+
+    private File _cachedFile;
+    private File _lastAccessedFile;
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java
new file mode 100644 (file)
index 0000000..860c72a
--- /dev/null
@@ -0,0 +1,72 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.*;
+
+/**
+ * Asserts the correct functionality of the {@link ResourceCache}.
+ */
+public class ResourceCacheTest
+{
+    @Before public void setupCache () throws IOException {
+        _fileToCache = _folder.newFile("filetocache.jar");
+        _cache = new ResourceCache(_folder.newFolder(".cache"));
+    }
+
+    @Test public void shouldCacheFile () throws IOException
+    {
+        assertEquals("abc123.jar", cacheFile().getName());
+    }
+
+    private File cacheFile() throws IOException
+    {
+        return _cache.cacheFile(_fileToCache, "abc123", "abc123");
+    }
+
+    @Test public void shouldTrackFileUsage () throws IOException
+    {
+        String name = "abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
+        File lastAccessedFile = new File(cacheFile().getParentFile(), name);
+        assertTrue(lastAccessedFile.exists());
+    }
+
+    @Test public void shouldNotCacheTheSameFile () throws Exception
+    {
+        File cachedFile = cacheFile();
+        cachedFile.setLastModified(YESTERDAY);
+        long expectedLastModified = cachedFile.lastModified();
+        // caching it another time
+        File sameCachedFile = cacheFile();
+        assertEquals(expectedLastModified, sameCachedFile.lastModified());
+    }
+
+    @Test public void shouldRememberWhenFileWasRequested () throws Exception
+    {
+        File cachedFile = cacheFile();
+        String name = cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
+        File lastAccessedFile = new File(cachedFile.getParentFile(), name);
+        lastAccessedFile.setLastModified(YESTERDAY);
+        long lastAccessed = lastAccessedFile.lastModified();
+        // caching it another time
+        cacheFile();
+        assertTrue(lastAccessedFile.lastModified() > lastAccessed);
+    }
+
+    @Rule public TemporaryFolder _folder = new TemporaryFolder();
+
+    private File _fileToCache;
+    private ResourceCache _cache;
+
+    private static final long YESTERDAY = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java
new file mode 100644 (file)
index 0000000..5344f3b
--- /dev/null
@@ -0,0 +1,54 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.LinkedHashSet;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link ClassPath}.
+ */
+public class ClassPathTest
+{
+    @Before public void createJarsAndSetupClassPath () throws IOException
+    {
+        _firstJar = _folder.newFile("a.jar");
+        _secondJar = _folder.newFile("b.jar");
+
+        LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
+        classPathEntries.add(_firstJar);
+        classPathEntries.add(_secondJar);
+        _classPath = new ClassPath(classPathEntries);
+    }
+
+    @Test public void shouldCreateValidArgumentString ()
+    {
+        assertEquals(
+            _firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(),
+            _classPath.asArgumentString());
+    }
+
+    @Test public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException
+    {
+        URL[] actualUrls = _classPath.asUrls();
+        assertEquals(_firstJar, new File(actualUrls[0].toURI()));
+        assertEquals(_secondJar, new File(actualUrls[1].toURI()));
+    }
+
+    @Rule public TemporaryFolder _folder = new TemporaryFolder();
+
+    private File _firstJar, _secondJar;
+    private ClassPath _classPath;
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java
new file mode 100644 (file)
index 0000000..6178651
--- /dev/null
@@ -0,0 +1,142 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.io.File;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+public class EnvConfigTest {
+
+    static String CWD = System.getProperty("user.dir");
+    static String TESTID = "testid";
+    static String TESTBASE = "https://test.com/test";
+
+    private void debugNotes(List<EnvConfig.Note> notes) {
+        for (EnvConfig.Note note : notes) {
+            System.out.println(note.message);
+        }
+    }
+
+    private void checkNoNotes (List<EnvConfig.Note> notes) {
+        StringBuilder msg = new StringBuilder();
+        for (EnvConfig.Note note : notes) {
+            if (note.level != EnvConfig.Note.Level.INFO) {
+                msg.append("\n").append(note.message);
+            }
+        }
+        if (msg.length() > 0) {
+            fail("Unexpected notes:" + msg.toString());
+        }
+    }
+    private void checkDir (EnvConfig cfg) {
+        assertTrue(cfg != null);
+        assertEquals(new File(CWD), cfg.appDir);
+    }
+    private void checkNoAppId (EnvConfig cfg) {
+        assertNull(cfg.appId);
+    }
+    private void checkAppId (EnvConfig cfg, String appId) {
+        assertEquals(appId, cfg.appId);
+    }
+    private void checkAppBase (EnvConfig cfg, String appBase) {
+        assertEquals(appBase, cfg.appBase);
+    }
+    private void checkNoAppBase (EnvConfig cfg) {
+        assertNull(cfg.appBase);
+    }
+    private void checkNoAppArgs (EnvConfig cfg) {
+        assertTrue(cfg.appArgs.isEmpty());
+    }
+    private void checkAppArgs (EnvConfig cfg, String... args) {
+        assertEquals(Arrays.asList(args), cfg.appArgs);
+    }
+
+    @Test public void testArgvDir () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        String[] args = { CWD };
+        EnvConfig cfg = EnvConfig.create(args, notes);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkNoAppId(cfg);
+        checkNoAppBase(cfg);
+        checkNoAppArgs(cfg);
+    }
+
+    @Test public void testArgvDirId () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        String[] args = { CWD, TESTID };
+        EnvConfig cfg = EnvConfig.create(args, notes);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkAppId(cfg, TESTID);
+        checkNoAppBase(cfg);
+        checkNoAppArgs(cfg);
+    }
+
+    @Test public void testArgvDirArgs () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        String[] args = { CWD, "", "one", "two" };
+        EnvConfig cfg = EnvConfig.create(args, notes);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkNoAppId(cfg);
+        checkNoAppBase(cfg);
+        checkAppArgs(cfg, "one", "two");
+    }
+
+    @Test public void testArgvDirIdArgs () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        String[] args = { CWD, TESTID, "one", "two" };
+        EnvConfig cfg = EnvConfig.create(args, notes);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkAppId(cfg, TESTID);
+        checkNoAppBase(cfg);
+        checkAppArgs(cfg, "one", "two");
+    }
+
+    private EnvConfig sysPropsConfig (List<EnvConfig.Note> notes, String... keyVals) {
+        for (int ii = 0; ii < keyVals.length; ii += 2) {
+            System.setProperty(keyVals[ii], keyVals[ii+1]);
+        }
+        EnvConfig cfg = EnvConfig.create(new String[0], notes);
+        for (int ii = 0; ii < keyVals.length; ii += 2) {
+            System.clearProperty(keyVals[ii]);
+        }
+        return cfg;
+    }
+
+    @Test public void testSysPropsDir () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkNoAppId(cfg);
+        checkNoAppBase(cfg);
+        checkNoAppArgs(cfg);
+    }
+
+    @Test public void testSysPropsDirIdBase () {
+        List<EnvConfig.Note> notes = new ArrayList<>();
+        EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD, "appid", TESTID, "appbase", TESTBASE);
+        // debugNotes(notes);
+        checkNoNotes(notes);
+        checkDir(cfg);
+        checkAppId(cfg, TESTID);
+        checkAppBase(cfg, TESTBASE);
+        checkNoAppArgs(cfg);
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java
new file mode 100644 (file)
index 0000000..7f35094
--- /dev/null
@@ -0,0 +1,70 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.assertEquals;
+
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PathBuilderTest
+{
+    @Before public void setupFilesAndResources () throws IOException
+    {
+        _firstJarFile = _appdir.newFile("a.jar");
+        _secondJarFile = _appdir.newFile("b.jar");
+
+        when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile);
+        when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile);
+        when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar));
+        when(_application.getAppDir()).thenReturn(_appdir.getRoot());
+    }
+
+    @Test public void shouldBuildDefaultClassPath () throws IOException
+    {
+        ClassPath classPath = PathBuilder.buildDefaultClassPath(_application);
+        String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator +
+            _secondJarFile.getAbsolutePath();
+        assertEquals(expectedClassPath, classPath.asArgumentString());
+    }
+
+    @Test public void shouldBuildCachedClassPath () throws IOException
+    {
+        when(_application.getDigest(_firstJar)).thenReturn("first");
+        when(_application.getDigest(_secondJar)).thenReturn("second");
+        when(_application.getCodeCacheRetentionDays()).thenReturn(1);
+
+        Path firstCachedJarFile = _appdir.getRoot().toPath().
+            resolve(PathBuilder.CODE_CACHE_DIR).resolve("fi").resolve("first.jar");
+
+        Path secondCachedJarFile = _appdir.getRoot().toPath().
+            resolve(PathBuilder.CODE_CACHE_DIR).resolve("se").resolve("second.jar");
+
+        String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator +
+            secondCachedJarFile.toAbsolutePath();
+
+        ClassPath classPath = PathBuilder.buildCachedClassPath(_application);
+        assertEquals(expectedClassPath, classPath.asArgumentString());
+    }
+
+    @Mock protected Application _application;
+    @Mock protected Resource _firstJar;
+    @Mock protected Resource _secondJar;
+
+    protected File _firstJarFile, _secondJarFile;
+
+    @Rule public TemporaryFolder _appdir = new TemporaryFolder();
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java
new file mode 100644 (file)
index 0000000..042a13f
--- /dev/null
@@ -0,0 +1,63 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+public class SysPropsTest {
+
+    @After public void clearProps () {
+        System.clearProperty("delay");
+        System.clearProperty("appbase_domain");
+        System.clearProperty("appbase_override");
+    }
+
+    private static final String[] APPBASES = {
+        "http://foobar.com/myapp",
+        "https://foobar.com/myapp",
+        "http://foobar.com:8080/myapp",
+        "https://foobar.com:8080/myapp"
+    };
+
+    @Test public void testStartDelay () {
+
+        assertEquals(0, SysProps.startDelay());
+
+        System.setProperty("delay", "x");
+        assertEquals(0, SysProps.startDelay());
+
+        System.setProperty("delay", "-7");
+        assertEquals(0, SysProps.startDelay());
+
+        System.setProperty("delay", "7");
+        assertEquals(7, SysProps.startDelay());
+
+        System.setProperty("delay", "1440");
+        assertEquals(1440, SysProps.startDelay());
+
+        System.setProperty("delay", "1441");
+        assertEquals(1440, SysProps.startDelay());
+    }
+
+    @Test public void testAppbaseDomain () {
+        System.setProperty("appbase_domain", "https://barbaz.com");
+        for (String appbase : APPBASES) {
+            assertEquals("https://barbaz.com/myapp", SysProps.overrideAppbase(appbase));
+        }
+        System.setProperty("appbase_domain", "http://barbaz.com");
+        for (String appbase : APPBASES) {
+            assertEquals("http://barbaz.com/myapp", SysProps.overrideAppbase(appbase));
+        }
+    }
+
+    @Test public void testAppbaseOverride () {
+        System.setProperty("appbase_override", "https://barbaz.com/newapp");
+        for (String appbase : APPBASES) {
+            assertEquals("https://barbaz.com/newapp", SysProps.overrideAppbase(appbase));
+        }
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java
new file mode 100644 (file)
index 0000000..7aa48ee
--- /dev/null
@@ -0,0 +1,23 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests {@link Color}.
+ */
+public class ColorTest
+{
+    @Test
+    public void testBrightness() {
+        assertEquals(0, Color.brightness(0xFF000000), 0.0000001);
+        assertEquals(1, Color.brightness(0xFFFFFFFF), 0.0000001);
+        assertEquals(0.0117647, Color.brightness(0xFF010203), 0.0000001);
+        assertEquals(1, Color.brightness(0xFF00FFC8), 0.0000001);
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java
new file mode 100644 (file)
index 0000000..cdc5a91
--- /dev/null
@@ -0,0 +1,171 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link Config}.
+ */
+public class ConfigTest
+{
+    public static class Pair {
+        public final String key;
+        public final String value;
+        public Pair (String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+    }
+
+    public static final Pair[] SIMPLE_PAIRS = {
+        new Pair("one", "two"),
+        new Pair("three", "four"),
+        new Pair("five", "six"),
+        new Pair("seven", "eight"),
+        new Pair("nine", "ten"),
+    };
+
+    @Test public void testSimplePairs () throws IOException
+    {
+        List<String[]> pairs = Config.parsePairs(
+            toReader(SIMPLE_PAIRS), Config.createOpts(true));
+        for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) {
+            assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]);
+            assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]);
+        }
+    }
+
+    @Test public void testQualifiedPairs () throws IOException
+    {
+        Pair linux = new Pair("one", "[linux] two");
+        Pair mac = new Pair("three", "[mac os x] four");
+        Pair linuxAndMac = new Pair("five", "[linux, mac os x] six");
+        Pair linux64 = new Pair("seven", "[linux-x86_64] eight");
+        Pair linux64s = new Pair("nine", "[linux-x86_64, linux-amd64] ten");
+        Pair mac64 = new Pair("eleven", "[mac os x-x86_64] twelve");
+        Pair win64 = new Pair("thirteen", "[windows-x86_64] fourteen");
+        Pair notWin = new Pair("fifteen", "[!windows] sixteen");
+        Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin };
+
+        Config.ParseOpts opts = Config.createOpts(false);
+        opts.osname = "linux";
+        opts.osarch = "i386";
+        List<String[]> parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(!exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(exists(parsed, notWin.key));
+
+        opts.osarch = "x86_64";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(exists(parsed, linuxAndMac.key));
+        assertTrue(exists(parsed, linux64.key));
+        assertTrue(exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(exists(parsed, notWin.key));
+
+        opts.osarch = "amd64";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(exists(parsed, notWin.key));
+
+        opts.osname = "mac os x";
+        opts.osarch = "x86_64";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(!exists(parsed, linux.key));
+        assertTrue(exists(parsed, mac.key));
+        assertTrue(exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(!exists(parsed, linux64s.key));
+        assertTrue(exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(exists(parsed, notWin.key));
+
+        opts.osname = "windows";
+        opts.osarch = "i386";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(!exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(!exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(!exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(!exists(parsed, notWin.key));
+
+        opts.osarch = "x86_64";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(!exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(!exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(!exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(exists(parsed, win64.key));
+        assertTrue(!exists(parsed, notWin.key));
+
+        opts.osarch = "amd64";
+        parsed = Config.parsePairs(toReader(pairs), opts);
+        assertTrue(!exists(parsed, linux.key));
+        assertTrue(!exists(parsed, mac.key));
+        assertTrue(!exists(parsed, linuxAndMac.key));
+        assertTrue(!exists(parsed, linux64.key));
+        assertTrue(!exists(parsed, linux64s.key));
+        assertTrue(!exists(parsed, mac64.key));
+        assertTrue(!exists(parsed, win64.key));
+        assertTrue(!exists(parsed, notWin.key));
+    }
+
+    protected static boolean exists (List<String[]> pairs, String key)
+    {
+        for (String[] pair : pairs) {
+            if (pair[0].equals(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static StringReader toReader (Pair[] pairs)
+    {
+        StringBuilder builder = new StringBuilder();
+        for (Pair pair : pairs) {
+            // throw some whitespace in to ensure it's trimmed
+            builder.append(whitespace()).append(pair.key).
+                append(whitespace()).append("=").
+                append(whitespace()).append(pair.value).
+                append(whitespace()).append("\n");
+        }
+        return new StringReader(builder.toString());
+    }
+
+    protected static String whitespace ()
+    {
+        return _rando.nextBoolean() ? " " : "";
+    }
+
+    protected static Random _rando = new Random();
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java
new file mode 100644 (file)
index 0000000..cfd53a2
--- /dev/null
@@ -0,0 +1,60 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+
+import org.junit.*;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link FileUtil}.
+ */
+public class FileUtilTest
+{
+    @Test public void testReadLines () throws IOException
+    {
+        String data = "This is a test\nof a file with\na few lines\n";
+        List<String> lines = FileUtil.readLines(new StringReader(data));
+        String[] linesBySplit = data.split("\n");
+        assertEquals(linesBySplit.length, lines.size());
+        for (int ii = 0; ii < lines.size(); ii++) {
+            assertEquals(linesBySplit[ii], lines.get(ii));
+        }
+    }
+
+    @Test public void shouldCopyFile () throws IOException
+    {
+        File source = _folder.newFile("source.txt");
+        File target = new File(_folder.getRoot(), "target.txt");
+        assertFalse(target.exists());
+        FileUtil.copy(source, target);
+        assertTrue(target.exists());
+    }
+
+    @Test public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException
+    {
+        _folder.newFile("a.txt");
+        new File(_folder.newFolder("b"), "b.txt").createNewFile();
+
+        class CountingVisitor implements FileUtil.Visitor {
+            int fileCount = 0;
+            @Override public void visit(File file) {
+                fileCount++;
+            }
+        }
+        CountingVisitor visitor = new CountingVisitor();
+        FileUtil.walkTree(_folder.getRoot(), visitor);
+        assertEquals(3, visitor.fileCount);
+    }
+
+    @Rule public TemporaryFolder _folder = new TemporaryFolder();
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java
new file mode 100644 (file)
index 0000000..703afef
--- /dev/null
@@ -0,0 +1,159 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link HostWhitelist}.
+ */
+public class HostWhitelistTest
+{
+    @Test
+    public void testVerify () throws MalformedURLException
+    {
+        checkCanVerify("foo.com", "http://foo.com", true);
+        checkCanVerify("foo.com", "http://foo.com/", true);
+        checkCanVerify("foo.com", "http://foo.com/x/y/z", true);
+        checkCanVerify("foo.com", "http://www.foo.com", false);
+        checkCanVerify("foo.com", "http://www.foo.com/", false);
+        checkCanVerify("foo.com", "http://www.foo.com/x/y/z", false);
+        checkCanVerify("foo.com", "http://a.b.foo.com", false);
+        checkCanVerify("foo.com", "http://a.b.foo.com/", false);
+        checkCanVerify("foo.com", "http://a.b.foo.com/x/y/z", false);
+        checkCanVerify("foo.com", "http://oo.com", false);
+        checkCanVerify("foo.com", "http://f.oo.com", false);
+        checkCanVerify("foo.com", "http://a.f.oo.com", false);
+
+        checkCanVerify("*.foo.com", "http://foo.com", false);
+        checkCanVerify("*.foo.com", "http://foo.com/", false);
+        checkCanVerify("*.foo.com", "http://foo.com/x/y/z", false);
+        checkCanVerify("*.foo.com", "http://www.foo.com", true);
+        checkCanVerify("*.foo.com", "http://www.foo.com/", true);
+        checkCanVerify("*.foo.com", "http://www.foo.com/x/y/z", true);
+        checkCanVerify("*.foo.com", "http://a.b.foo.com", true);
+        checkCanVerify("*.foo.com", "http://a.b.foo.com/", true);
+        checkCanVerify("*.foo.com", "http://a.b.foo.com/x/y/z", true);
+        checkCanVerify("*.foo.com", "http://oo.com", false);
+        checkCanVerify("*.foo.com", "http://f.oo.com", false);
+        checkCanVerify("*.foo.com", "http://a.f.oo.com", false);
+
+        checkCanVerify("*.com", "http://foo.com", true);
+        checkCanVerify("*.com", "http://foo.com/", true);
+        checkCanVerify("*.com", "http://foo.com/x/y/z", true);
+        checkCanVerify("*.com", "http://www.foo.com", true);
+        checkCanVerify("*.com", "http://www.foo.com/", true);
+        checkCanVerify("*.com", "http://www.foo.com/x/y/z", true);
+        checkCanVerify("*.com", "http://a.b.foo.com", true);
+        checkCanVerify("*.com", "http://a.b.foo.com/", true);
+        checkCanVerify("*.com", "http://a.b.foo.com/x/y/z", true);
+        checkCanVerify("*.com", "http://oo.com", true);
+        checkCanVerify("*.com", "http://f.oo.com", true);
+        checkCanVerify("*.com", "http://a.f.oo.com", true);
+
+        checkCanVerify("*.net", "http://foo.com", false);
+        checkCanVerify("*.net", "http://foo.com/", false);
+        checkCanVerify("*.net", "http://foo.com/x/y/z", false);
+        checkCanVerify("*.net", "http://www.foo.com", false);
+        checkCanVerify("*.net", "http://www.foo.com/", false);
+        checkCanVerify("*.net", "http://www.foo.com/x/y/z", false);
+        checkCanVerify("*.net", "http://a.b.foo.com", false);
+        checkCanVerify("*.net", "http://a.b.foo.com/", false);
+        checkCanVerify("*.net", "http://a.b.foo.com/x/y/z", false);
+        checkCanVerify("*.net", "http://oo.com", false);
+        checkCanVerify("*.net", "http://f.oo.com", false);
+        checkCanVerify("*.net", "http://a.f.oo.com", false);
+
+        checkCanVerify("www.*.com", "http://foo.com", false);
+        checkCanVerify("www.*.com", "http://foo.com/", false);
+        checkCanVerify("www.*.com", "http://foo.com/x/y/z", false);
+        checkCanVerify("www.*.com", "http://www.foo.com", true);
+        checkCanVerify("www.*.com", "http://www.foo.com/", true);
+        checkCanVerify("www.*.com", "http://www.foo.com/x/y/z", true);
+        checkCanVerify("www.*.com", "http://a.b.foo.com", false);
+        checkCanVerify("www.*.com", "http://a.b.foo.com/", false);
+        checkCanVerify("www.*.com", "http://a.b.foo.com/x/y/z", false);
+        checkCanVerify("www.*.com", "http://oo.com", false);
+        checkCanVerify("www.*.com", "http://f.oo.com", false);
+        checkCanVerify("www.*.com", "http://a.f.oo.com", false);
+        checkCanVerify("www.*.com", "http://www.a.f.oo.com", true);
+
+        checkCanVerify("foo.*", "http://foo.com", true);
+        checkCanVerify("foo.*", "http://foo.com/", true);
+        checkCanVerify("foo.*", "http://foo.com/x/y/z", true);
+        checkCanVerify("foo.*", "http://www.foo.com", false);
+        checkCanVerify("foo.*", "http://www.foo.com/", false);
+        checkCanVerify("foo.*", "http://www.foo.com/x/y/z", false);
+        checkCanVerify("foo.*", "http://a.b.foo.com", false);
+        checkCanVerify("foo.*", "http://a.b.foo.com/", false);
+        checkCanVerify("foo.*", "http://a.b.foo.com/x/y/z", false);
+        checkCanVerify("foo.*", "http://oo.com", false);
+        checkCanVerify("foo.*", "http://f.oo.com", false);
+        checkCanVerify("foo.*", "http://a.f.oo.com", false);
+
+        checkCanVerify("*.foo.*", "http://foo.com", false);
+        checkCanVerify("*.foo.*", "http://foo.com/", false);
+        checkCanVerify("*.foo.*", "http://foo.com/x/y/z", false);
+        checkCanVerify("*.foo.*", "http://www.foo.com", true);
+        checkCanVerify("*.foo.*", "http://www.foo.com/", true);
+        checkCanVerify("*.foo.*", "http://www.foo.com/x/y/z", true);
+        checkCanVerify("*.foo.*", "http://a.b.foo.com", true);
+        checkCanVerify("*.foo.*", "http://a.b.foo.com/", true);
+        checkCanVerify("*.foo.*", "http://a.b.foo.com/x/y/z", true);
+        checkCanVerify("*.foo.*", "http://oo.com", false);
+        checkCanVerify("*.foo.*", "http://f.oo.com", false);
+        checkCanVerify("*.foo.*", "http://a.f.oo.com", false);
+
+        checkCanVerify("127.0.0.1", "http://127.0.0.1", true);
+        checkCanVerify("127.0.0.1", "http://127.0.0.1/", true);
+        checkCanVerify("127.0.0.1", "http://127.0.0.1/x/y/z", true);
+        checkCanVerify("*.0.0.1", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.*.0.1", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.0.*.1", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.0.0.*", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.*.1", "http://127.0.0.1/abc", true);
+        checkCanVerify("*.0.1", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.0.*", "http://127.0.0.1/abc", true);
+        checkCanVerify("*", "http://127.0.0.1/abc", true);
+        checkCanVerify("127.0.0.2", "http://127.0.0.1", false);
+        checkCanVerify("127.0.2.1", "http://127.0.0.1", false);
+        checkCanVerify("127.2.0.1", "http://127.0.0.1", false);
+        checkCanVerify("222.0.0.1", "http://127.0.0.1", false);
+
+        checkCanVerify("", "http://foo.com", true);
+        checkCanVerify("", "http://aaa.bbb.net/xyz", true);
+        checkCanVerify("", "https://127.0.0.1/abc", true);
+
+        checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://aaa.bbb.com/m", true);
+        checkCanVerify("aaa.bbb.com, xxx.yyy.com,*.jjj.net", "http://xxx.yyy.com/n", true);
+        checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://www.jjj.net/o", true);
+    }
+
+    private static void checkCanVerify (String whitelist, String url, boolean expectedToPass)
+        throws MalformedURLException
+    {
+        List<String> w = Arrays.asList(StringUtil.parseStringArray(whitelist));
+        URL u = new URL(url);
+        boolean passed;
+
+        try {
+            HostWhitelist.verify(w, u);
+            passed = true;
+        } catch (MalformedURLException e) {
+            passed = false;
+        }
+
+        assertEquals("with whitelist '" + whitelist + "' and URL '" + url + "'",
+            expectedToPass, passed);
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java
new file mode 100644 (file)
index 0000000..f70bab9
--- /dev/null
@@ -0,0 +1,28 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import org.junit.Test;
+
+import static com.threerings.getdown.util.StringUtil.couldBeValidUrl;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link StringUtil}.
+ */
+public class StringUtilTest
+{
+    @Test public void testCouldBeValidUrl ()
+    {
+        assertTrue(couldBeValidUrl("http://www.foo.com/"));
+        assertTrue(couldBeValidUrl("http://www.foo.com/A-B-C/1_2_3/~bar/q.jsp?x=u+i&y=2;3;4#baz%20baz"));
+        assertTrue(couldBeValidUrl("https://user:secret@www.foo.com/"));
+
+        assertFalse(couldBeValidUrl("http://www.foo.com & echo hello"));
+        assertFalse(couldBeValidUrl("http://www.foo.com\""));
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java
new file mode 100644 (file)
index 0000000..165fbe3
--- /dev/null
@@ -0,0 +1,53 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class VersionUtilTest {
+
+    @Test
+    public void shouldParseJavaVersion ()
+    {
+        long version = VersionUtil.parseJavaVersion(
+            "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8.0_152");
+        assertEquals(1_080_152, version);
+    }
+
+    @Test
+    public void shouldParseJavaVersion8 ()
+    {
+        long version = VersionUtil.parseJavaVersion(
+            "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8");
+        assertEquals(1_080_000, version);
+    }
+
+    @Test
+    public void shouldParseJavaVersion9 ()
+    {
+        long version = VersionUtil.parseJavaVersion(
+            "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "9");
+        assertEquals(9_000_000, version);
+    }
+
+    @Test
+    public void shouldParseJavaVersion10 ()
+    {
+        long version = VersionUtil.parseJavaVersion(
+            "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "10");
+        assertEquals(10_000_000, version);
+    }
+
+    @Test
+    public void shouldParseJavaRuntimeVersion ()
+    {
+        long version = VersionUtil.parseJavaVersion(
+            "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?", "1.8.0_131-b11");
+        assertEquals(108_013_111, version);
+    }
+}
diff --git a/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644 (file)
index 0000000..1f0955d
--- /dev/null
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/getdown/src/getdown/launcher/.project-MOVED b/getdown/src/getdown/launcher/.project-MOVED
new file mode 100644 (file)
index 0000000..d77a6e8
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>getdown-launcher</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.m2e.core.maven2Builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.m2e.core.maven2Nature</nature>
+       </natures>
+</projectDescription>
diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..abdea9a
--- /dev/null
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding/<project>=UTF-8
diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..54e5672
--- /dev/null
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs
new file mode 100644 (file)
index 0000000..f897a7f
--- /dev/null
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/getdown/src/getdown/launcher/pom.xml b/getdown/src/getdown/launcher/pom.xml
new file mode 100644 (file)
index 0000000..10e1aa6
--- /dev/null
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.threerings.getdown</groupId>
+    <artifactId>getdown</artifactId>
+    <version>1.8.3-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>getdown-launcher</artifactId>
+  <packaging>jar</packaging>
+  <name>Getdown Launcher</name>
+  <description>The Getdown app updater/launcher</description>
+
+  <repositories>
+    <repository>
+      <id>lib-repo</id>
+      <url>file://${basedir}/../lib</url>
+    </repository>
+  </repositories>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.threerings.getdown</groupId>
+      <artifactId>getdown-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.samskivert</groupId>
+      <artifactId>samskivert</artifactId>
+      <version>1.2</version>
+    </dependency>
+    <dependency>
+      <groupId>jregistrykey</groupId>
+      <artifactId>jregistrykey</artifactId>
+      <version>1.0</version>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>com.github.wvengen</groupId>
+        <artifactId>proguard-maven-plugin</artifactId>
+        <version>2.0.14</version>
+        <executions>
+         <execution>
+           <phase>package</phase>
+           <goals>
+             <goal>proguard</goal>
+           </goals>
+         </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>net.sf.proguard</groupId>
+            <artifactId>proguard-base</artifactId>
+            <version>6.0.3</version>
+            <scope>runtime</scope>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <proguardVersion>6.0.3</proguardVersion>
+          <outputDirectory>${project.build.directory}</outputDirectory>
+          <outjar>${project.build.finalName}.jar</outjar>
+          <injar>${project.build.finalName}.jar</injar>
+          <assembly>
+            <inclusions>
+              <inclusion>
+                <groupId>com.threerings.getdown</groupId>
+                <artifactId>getdown-core</artifactId>
+              </inclusion>
+              <inclusion>
+                <groupId>com.samskivert</groupId>
+                <artifactId>samskivert</artifactId>
+                <filter>
+                  !**/*.java,
+                  !**/swing/RuntimeAdjust*,
+                  !**/swing/util/ButtonUtil*,
+                  !**/util/CalendarUtil*,
+                  !**/util/Calendars*,
+                  !**/util/Log4JLogger*,
+                  !**/util/PrefsConfig*,
+                  !**/util/SignalUtil*,
+                  com/samskivert/Log.class,
+                  **/samskivert/io/**,
+                  **/samskivert/swing/**,
+                  **/samskivert/text/**,
+                  **/samskivert/util/**
+                </filter>
+              </inclusion>
+              <inclusion>
+                <groupId>jregistrykey</groupId>
+                <artifactId>jregistrykey</artifactId>
+              </inclusion>
+            </inclusions>
+          </assembly>
+          <obfuscate>true</obfuscate>
+          <options>
+            <option>-keep public class com.threerings.getdown.** { *; }</option>
+            <option>-keep public class ca.beq.util.win32.registry.** { *; }</option>
+            <option>-keepattributes Exceptions, InnerClasses, Signature</option>
+          </options>
+          <libs>
+            <lib>${rt.jar.path}</lib>
+          </libs>
+          <addMavenDescriptor>false</addMavenDescriptor>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>3.1.0</version>
+        <configuration>
+          <archive>
+            <manifest>
+              <mainClass>com.threerings.getdown.launcher.GetdownApp</mainClass>
+            </manifest>
+            <manifestEntries>
+              <Permissions>all-permissions</Permissions>
+              <Application-Name>Getdown</Application-Name>
+              <Codebase>*</Codebase>
+              <Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
+              <Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
+              <Trusted-Library>true</Trusted-Library>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>3.2.1</version>
+        <configuration>
+          <!-- put your configurations here -->
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+
+
+
+    </plugins>
+  </build>
+
+  <profiles>
+    <!-- finagling to find rt.jar -->
+    <profile>
+      <id>non-mac-jre</id>
+      <activation>
+        <file><exists>${java.home}/../lib/rt.jar</exists></file>
+      </activation>
+      <properties>
+        <rt.jar.path>${java.home}/../lib/rt.jar</rt.jar.path>
+      </properties>
+    </profile>
+    <profile>
+      <id>non-mac-jdk</id>
+      <activation>
+        <file><exists>${java.home}/lib/rt.jar</exists></file>
+      </activation>
+      <properties>
+        <rt.jar.path>${java.home}/lib/rt.jar</rt.jar.path>
+      </properties>
+    </profile>
+    <profile>
+      <id>java-9-jdk</id>
+      <activation>
+        <file><exists>${java.home}/jmods/java.base.jmod</exists></file>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>com.github.wvengen</groupId>
+            <artifactId>proguard-maven-plugin</artifactId>
+            <configuration>
+              <libs>
+                <lib>${java.home}/jmods/java.base.jmod</lib>
+                <lib>${java.home}/jmods/java.desktop.jmod</lib>
+                <lib>${java.home}/jmods/java.logging.jmod</lib>
+                <lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
+              </libs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java
new file mode 100644 (file)
index 0000000..dc1e54e
--- /dev/null
@@ -0,0 +1,100 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.samskivert.swing.GroupLayout;
+import com.samskivert.swing.Spacer;
+import com.samskivert.swing.VGroupLayout;
+
+import com.threerings.getdown.util.MessageUtil;
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Displays a confirmation that the user wants to abort installation.
+ */
+public final class AbortPanel extends JFrame
+    implements ActionListener
+{
+    public AbortPanel (Getdown getdown, ResourceBundle msgs)
+    {
+        _getdown = getdown;
+        _msgs = msgs;
+
+        setLayout(new VGroupLayout());
+        setResizable(false);
+        setTitle(get("m.abort_title"));
+
+        JLabel message = new JLabel(get("m.abort_confirm"));
+        message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+        add(message);
+        add(new Spacer(5, 5));
+
+        JPanel row = GroupLayout.makeButtonBox(GroupLayout.CENTER);
+        JButton button;
+        row.add(button = new JButton(get("m.abort_ok")));
+        button.setActionCommand("ok");
+        button.addActionListener(this);
+        row.add(button = new JButton(get("m.abort_cancel")));
+        button.setActionCommand("cancel");
+        button.addActionListener(this);
+        getRootPane().setDefaultButton(button);
+        add(row);
+    }
+
+    // documentation inherited
+    @Override
+    public Dimension getPreferredSize ()
+    {
+        // this is annoyingly hardcoded, but we can't just force the width
+        // or the JLabel will claim a bogus height thinking it can lay its
+        // text out all on one line which will booch the whole UI's
+        // preferred size
+        return new Dimension(300, 200);
+    }
+
+    // documentation inherited from interface
+    public void actionPerformed (ActionEvent e)
+    {
+        String cmd = e.getActionCommand();
+        if (cmd.equals("ok")) {
+            System.exit(0);
+        } else {
+            setVisible(false);
+        }
+    }
+
+    /** Used to look up localized messages. */
+    protected String get (String key)
+    {
+        // if this string is tainted, we don't translate it, instead we
+        // simply remove the taint character and return it to the caller
+        if (MessageUtil.isTainted(key)) {
+            return MessageUtil.untaint(key);
+        }
+        try {
+            return _msgs.getString(key);
+        } catch (MissingResourceException mre) {
+            log.warning("Missing translation message '" + key + "'.");
+            return key;
+        }
+    }
+
+    protected Getdown _getdown;
+    protected ResourceBundle _msgs;
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java
new file mode 100644 (file)
index 0000000..66629f2
--- /dev/null
@@ -0,0 +1,1075 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+import javax.imageio.ImageIO;
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLayeredPane;
+
+import com.samskivert.swing.util.SwingUtil;
+import com.threerings.getdown.data.*;
+import com.threerings.getdown.data.Application.UpdateInterface.Step;
+import com.threerings.getdown.net.Downloader;
+import com.threerings.getdown.net.HTTPDownloader;
+import com.threerings.getdown.tools.Patcher;
+import com.threerings.getdown.util.*;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Manages the main control for the Getdown application updater and deployment system.
+ */
+public abstract class Getdown extends Thread
+    implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
+{
+    public Getdown (EnvConfig envc)
+    {
+        super("Getdown");
+        try {
+            // If the silent property exists, install without bringing up any gui. If it equals
+            // launch, start the application after installing. Otherwise, just install and exit.
+            _silent = SysProps.silent();
+            if (_silent) {
+                _launchInSilent = SysProps.launchInSilent();
+                _noUpdate = SysProps.noUpdate();
+            }
+            // If we're running in a headless environment and have not otherwise customized
+            // silence, operate without a UI and do launch the app.
+            if (!_silent && GraphicsEnvironment.isHeadless()) {
+                log.info("Running in headless JVM, will attempt to operate without UI.");
+                _silent = true;
+                _launchInSilent = true;
+            }
+            _delay = SysProps.startDelay();
+        } catch (SecurityException se) {
+            // don't freak out, just assume non-silent and no delay; we're probably already
+            // recovering from a security failure
+        }
+        try {
+            _msgs = ResourceBundle.getBundle("com.threerings.getdown.messages");
+        } catch (Exception e) {
+            // welcome to hell, where java can't cope with a classpath that contains jars that live
+            // in a directory that contains a !, at least the same bug happens on all platforms
+            String dir = envc.appDir.toString();
+            if (dir.equals(".")) {
+                dir = System.getProperty("user.dir");
+            }
+            String errmsg = "The directory in which this application is installed:\n" + dir +
+                "\nis invalid (" + e.getMessage() + "). If the full path to the app directory " +
+                "contains the '!' character, this will trigger this error.";
+            fail(errmsg);
+        }
+        _app = new Application(envc);
+        _startup = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns true if there are pending new resources, waiting to be installed.
+     */
+    public boolean isUpdateAvailable ()
+    {
+        return _readyToInstall && !_toInstallResources.isEmpty();
+    }
+
+    /**
+     * Installs the currently pending new resources.
+     */
+    public void install () throws IOException
+    {
+        if (SysProps.noInstall()) {
+            log.info("Skipping install due to 'no_install' sysprop.");
+        } else if (_readyToInstall) {
+            log.info("Installing " + _toInstallResources.size() + " downloaded resources:");
+            for (Resource resource : _toInstallResources) {
+                resource.install(true);
+            }
+            _toInstallResources.clear();
+            _readyToInstall = false;
+            log.info("Install completed.");
+        } else {
+            log.info("Nothing to install.");
+        }
+    }
+
+    @Override
+    public void run ()
+    {
+        // if we have no messages, just bail because we're hosed; the error message will be
+        // displayed to the user already
+        if (_msgs == null) {
+            return;
+        }
+
+        log.info("Getdown starting", "version", Build.version(), "built", Build.time());
+
+        // determine whether or not we can write to our install directory
+        File instdir = _app.getLocalPath("");
+        if (!instdir.canWrite()) {
+            String path = instdir.getPath();
+            if (path.equals(".")) {
+                path = System.getProperty("user.dir");
+            }
+            fail(MessageUtil.tcompose("m.readonly_error", path));
+            return;
+        }
+
+        try {
+            _dead = false;
+            // if we fail to detect a proxy, but we're allowed to run offline, then go ahead and
+            // run the app anyway because we're prepared to cope with not being able to update
+            if (detectProxy() || _app.allowOffline()) {
+                getdown();
+            } else if (_silent) {
+                log.warning("Need a proxy, but we don't want to bother anyone.  Exiting.");
+            } else {
+                // create a panel they can use to configure the proxy settings
+                _container = createContainer();
+                // allow them to close the window to abort the proxy configuration
+                _dead = true;
+                configureContainer();
+                ProxyPanel panel = new ProxyPanel(this, _msgs);
+                // set up any existing configured proxy
+                String[] hostPort = ProxyUtil.loadProxy(_app);
+                panel.setProxy(hostPort[0], hostPort[1]);
+                _container.add(panel, BorderLayout.CENTER);
+                showContainer();
+            }
+
+        } catch (Exception e) {
+            log.warning("run() failed.", e);
+            String msg = e.getMessage();
+            if (msg == null) {
+                msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
+            } else if (!msg.startsWith("m.")) {
+                // try to do something sensible based on the type of error
+                if (e instanceof FileNotFoundException) {
+                    msg = MessageUtil.compose(
+                        "m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
+                } else {
+                    msg = MessageUtil.compose(
+                        "m.init_error", MessageUtil.taint(msg), _ifc.installError);
+                }
+            }
+            fail(msg);
+        }
+    }
+
+    /**
+     * Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
+     */
+    public void configProxy (String host, String port, String username, String password)
+    {
+        log.info("User configured proxy", "host", host, "port", port);
+
+        if (!StringUtil.isBlank(host)) {
+            ProxyUtil.configProxy(_app, host, port, username, password);
+        }
+
+        // clear out our UI
+        disposeContainer();
+        _container = null;
+
+        // fire up a new thread
+        new Thread(this).start();
+    }
+
+    protected boolean detectProxy () {
+        if (ProxyUtil.autoDetectProxy(_app)) {
+            return true;
+        }
+
+        // otherwise see if we actually need a proxy; first we have to initialize our application
+        // to get some sort of interface configuration and the appbase URL
+        log.info("Checking whether we need to use a proxy...");
+        try {
+            readConfig(true);
+        } catch (IOException ioe) {
+            // no worries
+        }
+        updateStatus("m.detecting_proxy");
+        if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) {
+            return false;
+        }
+
+        // we got through, so we appear not to require a proxy; make a blank proxy config so that
+        // we don't go through this whole detection process again next time
+        log.info("No proxy appears to be needed.");
+        ProxyUtil.saveProxy(_app, null, null);
+        return true;
+    }
+
+    protected void readConfig (boolean preloads) throws IOException {
+        Config config = _app.init(true);
+        if (preloads) doPredownloads(_app.getResources());
+        _ifc = new Application.UpdateInterface(config);
+    }
+
+    /**
+     * Downloads and installs (without verifying) any resources that are marked with a
+     * {@code PRELOAD} attribute.
+     * @param resources the full set of resources from the application (the predownloads will be
+     * extracted from it).
+     */
+    protected void doPredownloads (Collection<Resource> resources) {
+        List<Resource> predownloads = new ArrayList<>();
+        for (Resource rsrc : resources) {
+            if (rsrc.shouldPredownload() && !rsrc.getLocal().exists()) {
+                predownloads.add(rsrc);
+            }
+        }
+
+        try {
+            download(predownloads);
+            for (Resource rsrc : predownloads) {
+                rsrc.install(false); // install but don't validate yet
+            }
+        } catch (IOException ioe) {
+            log.warning("Failed to predownload resources. Continuing...", ioe);
+        }
+    }
+
+    /**
+     * Does the actual application validation, update and launching business.
+     */
+    protected void getdown ()
+    {
+        try {
+            // first parses our application deployment file
+            try {
+                readConfig(true);
+            } catch (IOException ioe) {
+                log.warning("Failed to initialize: " + ioe);
+                _app.attemptRecovery(this);
+                // and re-initalize
+                readConfig(true);
+                // and force our UI to be recreated with the updated info
+                createInterfaceAsync(true);
+            }
+            if (!_noUpdate && !_app.lockForUpdates()) {
+                throw new MultipleGetdownRunning();
+            }
+
+            // Update the config modtime so a sleeping getdown will notice the change.
+            File config = _app.getLocalPath(Application.CONFIG_FILE);
+            if (!config.setLastModified(System.currentTimeMillis())) {
+                log.warning("Unable to set modtime on config file, will be unable to check for " +
+                            "another instance of getdown running while this one waits.");
+            }
+            if (_delay > 0) {
+                // don't hold the lock while waiting, let another getdown proceed if it starts.
+                _app.releaseLock();
+                // Store the config modtime before waiting the delay amount of time
+                long lastConfigModtime = config.lastModified();
+                log.info("Waiting " + _delay + " minutes before beginning actual work.");
+                Thread.sleep(_delay * 60 * 1000);
+                if (lastConfigModtime < config.lastModified()) {
+                    log.warning("getdown.txt was modified while getdown was waiting.");
+                    throw new MultipleGetdownRunning();
+                }
+            }
+
+            // if no_update was specified, directly start the app without updating
+            if (_noUpdate) {
+                log.info("Launching without update!");
+                launch();
+                return;
+            }
+
+            // we create this tracking counter here so that we properly note the first time through
+            // the update process whether we previously had validated resources (which means this
+            // is not a first time install); we may, in the course of updating, wipe out our
+            // validation markers and revalidate which would make us think we were doing a fresh
+            // install if we didn't specifically remember that we had validated resources the first
+            // time through
+            int[] alreadyValid = new int[1];
+
+            // we'll keep track of all the resources we unpack
+            Set<Resource> unpacked = new HashSet<>();
+
+            _toInstallResources = new HashSet<>();
+            _readyToInstall = false;
+
+            // setStep(Step.START);
+            for (int ii = 0; ii < MAX_LOOPS; ii++) {
+                // make sure we have the desired version and that the metadata files are valid...
+                setStep(Step.VERIFY_METADATA);
+                setStatusAsync("m.validating", -1, -1L, false);
+                if (_app.verifyMetadata(this)) {
+                    log.info("Application requires update.");
+                    update();
+                    // loop back again and reverify the metadata
+                    continue;
+                }
+
+                // now verify (and download) our resources...
+                setStep(Step.VERIFY_RESOURCES);
+                setStatusAsync("m.validating", -1, -1L, false);
+                Set<Resource> toDownload = new HashSet<>();
+                _app.verifyResources(_progobs, alreadyValid, unpacked,
+                                     _toInstallResources, toDownload);
+
+                if (toDownload.size() > 0) {
+                    // we have resources to download, also note them as to-be-installed
+                    for (Resource r : toDownload) {
+                        if (!_toInstallResources.contains(r)) {
+                            _toInstallResources.add(r);
+                        }
+                    }
+
+                    try {
+                        // if any of our resources have already been marked valid this is not a
+                        // first time install and we don't want to enable tracking
+                        _enableTracking = (alreadyValid[0] == 0);
+                        reportTrackingEvent("app_start", -1);
+
+                        // redownload any that are corrupt or invalid...
+                        log.info(toDownload.size() + " of " + _app.getAllActiveResources().size() +
+                                 " rsrcs require update (" + alreadyValid[0] + " assumed valid).");
+                        setStep(Step.REDOWNLOAD_RESOURCES);
+                        download(toDownload);
+
+                        reportTrackingEvent("app_complete", -1);
+
+                    } finally {
+                        _enableTracking = false;
+                    }
+
+                    // now we'll loop back and try it all again
+                    continue;
+                }
+
+                // if we aren't running in a JVM that meets our version requirements, either
+                // complain or attempt to download and install the appropriate version
+                if (!_app.haveValidJavaVersion()) {
+                    // download and install the necessary version of java, then loop back again and
+                    // reverify everything; if we can't download java; we'll throw an exception
+                    log.info("Attempting to update Java VM...");
+                    setStep(Step.UPDATE_JAVA);
+                    _enableTracking = true; // always track JVM downloads
+                    try {
+                        updateJava();
+                    } finally {
+                        _enableTracking = false;
+                    }
+                    continue;
+                }
+
+                // if we were downloaded in full from another service (say, Steam), we may
+                // not have unpacked all of our resources yet
+                if (Boolean.getBoolean("check_unpacked")) {
+                    File ufile = _app.getLocalPath("unpacked.dat");
+                    long version = -1;
+                    long aversion = _app.getVersion();
+                    if (!ufile.exists()) {
+                        ufile.createNewFile();
+                    } else {
+                        version = VersionUtil.readVersion(ufile);
+                    }
+
+                    if (version < aversion) {
+                        log.info("Performing unpack", "version", version, "aversion", aversion);
+                        setStep(Step.UNPACK);
+                        updateStatus("m.validating");
+                        _app.unpackResources(_progobs, unpacked);
+                        try {
+                            VersionUtil.writeVersion(ufile, aversion);
+                        } catch (IOException ioe) {
+                            log.warning("Failed to update unpacked version", ioe);
+                        }
+                    }
+                }
+
+                // assuming we're not doing anything funny, install the update
+                _readyToInstall = true;
+                install();
+
+                // Only launch if we aren't in silent mode. Some mystery program starting out
+                // of the blue would be disconcerting.
+                if (!_silent || _launchInSilent) {
+                    // And another final check for the lock. It'll already be held unless
+                    // we're in silent mode.
+                    _app.lockForUpdates();
+                    launch();
+                }
+                return;
+            }
+
+            log.warning("Pants! We couldn't get the job done.");
+            throw new IOException("m.unable_to_repair");
+
+        } catch (Exception e) {
+            log.warning("getdown() failed.", e);
+            String msg = e.getMessage();
+            if (msg == null) {
+                msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
+            } else if (!msg.startsWith("m.")) {
+                // try to do something sensible based on the type of error
+                if (e instanceof FileNotFoundException) {
+                    msg = MessageUtil.compose(
+                        "m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
+                } else {
+                    msg = MessageUtil.compose(
+                        "m.init_error", MessageUtil.taint(msg), _ifc.installError);
+                }
+            }
+            // Since we're dead, clear off the 'time remaining' label along with displaying the
+            // error message
+            fail(msg);
+            _app.releaseLock();
+        }
+    }
+
+    // documentation inherited from interface
+    @Override
+    public void updateStatus (String message)
+    {
+        setStatusAsync(message, -1, -1L, true);
+    }
+
+    /**
+     * Load the image at the path. Before trying the exact path/file specified we will look to see
+     * if we can find a localized version by sticking a {@code _<language>} in front of the "." in
+     * the filename.
+     */
+    @Override
+    public BufferedImage loadImage (String path)
+    {
+        if (StringUtil.isBlank(path)) {
+            return null;
+        }
+
+        File imgpath = null;
+        try {
+            // First try for a localized image.
+            String localeStr = Locale.getDefault().getLanguage();
+            imgpath = _app.getLocalPath(path.replace(".", "_" + localeStr + "."));
+            return ImageIO.read(imgpath);
+        } catch (IOException ioe) {
+            // No biggie, we'll try the generic one.
+        }
+
+        // If that didn't work, try a generic one.
+        try {
+            imgpath = _app.getLocalPath(path);
+            return ImageIO.read(imgpath);
+        } catch (IOException ioe2) {
+            log.warning("Failed to load image", "path", imgpath, "error", ioe2);
+            return null;
+        }
+    }
+
+    /**
+     * Downloads and installs an Java VM bundled with the application. This is called if we are not
+     * running with the necessary Java version.
+     */
+    protected void updateJava ()
+        throws IOException
+    {
+        Resource vmjar = _app.getJavaVMResource();
+        if (vmjar == null) {
+            throw new IOException("m.java_download_failed");
+        }
+
+        reportTrackingEvent("jvm_start", -1);
+
+        updateStatus("m.downloading_java");
+        List<Resource> list = new ArrayList<>();
+        list.add(vmjar);
+        download(list);
+
+        reportTrackingEvent("jvm_unpack", -1);
+
+        updateStatus("m.unpacking_java");
+        vmjar.install(true);
+
+        // these only run on non-Windows platforms, so we use Unix file separators
+        String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/";
+        FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "bin/java"));
+        FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/jspawnhelper"));
+        FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/amd64/jspawnhelper"));
+
+        // lastly regenerate the .jsa dump file that helps Java to start up faster
+        String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath(""));
+        try {
+            log.info("Regenerating classes.jsa for " + vmpath + "...");
+            Runtime.getRuntime().exec(vmpath + " -Xshare:dump");
+        } catch (Exception e) {
+            log.warning("Failed to regenerate .jsa dump file", "error", e);
+        }
+
+        reportTrackingEvent("jvm_complete", -1);
+    }
+
+    /**
+     * Called if the application is determined to be of an old version.
+     */
+    protected void update ()
+        throws IOException
+    {
+        // first clear all validation markers
+        _app.clearValidationMarkers();
+
+        // attempt to download the patch files
+        Resource patch = _app.getPatchResource(null);
+        if (patch != null) {
+            List<Resource> list = new ArrayList<>();
+            list.add(patch);
+
+            // add the auxiliary group patch files for activated groups
+            for (Application.AuxGroup aux : _app.getAuxGroups()) {
+                if (_app.isAuxGroupActive(aux.name)) {
+                    patch = _app.getPatchResource(aux.name);
+                    if (patch != null) {
+                        list.add(patch);
+                    }
+                }
+            }
+
+            // show the patch notes button, if applicable
+            if (!StringUtil.isBlank(_ifc.patchNotesUrl)) {
+                createInterfaceAsync(false);
+                EventQueue.invokeLater(new Runnable() {
+                    public void run () {
+                        _patchNotes.setVisible(true);
+                    }
+                });
+            }
+
+            // download the patch files...
+            setStep(Step.DOWNLOAD);
+            download(list);
+
+            // and apply them...
+            setStep(Step.PATCH);
+            updateStatus("m.patching");
+
+            long[] sizes = new long[list.size()];
+            Arrays.fill(sizes, 1L);
+            ProgressAggregator pragg = new ProgressAggregator(_progobs, sizes);
+            int ii = 0; for (Resource prsrc : list) {
+                ProgressObserver pobs = pragg.startElement(ii++);
+                try {
+                    // install the patch file (renaming them from _new)
+                    prsrc.install(false);
+                    // now apply the patch
+                    Patcher patcher = new Patcher();
+                    patcher.patch(prsrc.getLocal().getParentFile(), prsrc.getLocal(), pobs);
+                } catch (Exception e) {
+                    log.warning("Failed to apply patch", "prsrc", prsrc, e);
+                }
+
+                // clean up the patch file
+                if (!FileUtil.deleteHarder(prsrc.getLocal())) {
+                    log.warning("Failed to delete '" + prsrc + "'.");
+                }
+            }
+        }
+
+        // if the patch resource is null, that means something was booched in the application, so
+        // we skip the patching process but update the metadata which will result in a "brute
+        // force" upgrade
+
+        // finally update our metadata files...
+        _app.updateMetadata();
+        // ...and reinitialize the application
+        readConfig(false);
+    }
+
+    /**
+     * Called if the application is determined to require resource downloads.
+     */
+    protected void download (Collection<Resource> resources)
+        throws IOException
+    {
+        // create our user interface
+        createInterfaceAsync(false);
+
+        Downloader dl = new HTTPDownloader(_app.proxy) {
+            @Override protected void resolvingDownloads () {
+                updateStatus("m.resolving");
+            }
+
+            @Override protected void downloadProgress (int percent, long remaining) {
+                // check for another getdown running at 0 and every 10% after that
+                if (_lastCheck == -1 || percent >= _lastCheck + 10) {
+                    if (_delay > 0) {
+                        // stop the presses if something else is holding the lock
+                        boolean locked = _app.lockForUpdates();
+                        _app.releaseLock();
+                        if (locked) abort();
+                    }
+                    _lastCheck = percent;
+                }
+                setStatusAsync("m.downloading", stepToGlobalPercent(percent), remaining, true);
+                if (percent > 0) {
+                    reportTrackingEvent("progress", percent);
+                }
+            }
+
+            @Override protected void downloadFailed (Resource rsrc, Exception e) {
+                updateStatus(MessageUtil.tcompose("m.failure", e.getMessage()));
+                log.warning("Download failed", "rsrc", rsrc, e);
+            }
+
+            /** The last percentage at which we checked for another getdown running, or -1 for not
+             * having checked at all. */
+            protected int _lastCheck = -1;
+        };
+        if (!dl.download(resources, _app.maxConcurrentDownloads())) {
+            // if we aborted due to detecting another getdown running, we want to report here
+            throw new MultipleGetdownRunning();
+        }
+    }
+
+    /**
+     * Called to launch the application if everything is determined to be ready to go.
+     */
+    protected void launch ()
+    {
+        setStep(Step.LAUNCH);
+        setStatusAsync("m.launching", stepToGlobalPercent(100), -1L, false);
+
+        try {
+            if (invokeDirect()) {
+                // we want to close the Getdown window, as the app is launching
+                disposeContainer();
+                _app.releaseLock();
+                _app.invokeDirect();
+
+            } else {
+                Process proc;
+                if (_app.hasOptimumJvmArgs()) {
+                    // if we have "optimum" arguments, we want to try launching with them first
+                    proc = _app.createProcess(true);
+
+                    long fallback = System.currentTimeMillis() + FALLBACK_CHECK_TIME;
+                    boolean error = false;
+                    while (fallback > System.currentTimeMillis()) {
+                        try {
+                            error = proc.exitValue() != 0;
+                            break;
+                        } catch (IllegalThreadStateException e) {
+                            Thread.yield();
+                        }
+                    }
+
+                    if (error) {
+                        log.info("Failed to launch with optimum arguments; falling back.");
+                        proc = _app.createProcess(false);
+                    }
+                } else {
+                    proc = _app.createProcess(false);
+                }
+
+                // close standard in to avoid choking standard out of the launched process
+                proc.getInputStream().close();
+                // close standard out, since we're not going to write to anything to it anyway
+                proc.getOutputStream().close();
+
+                // on Windows 98 and ME we need to stick around and read the output of stderr lest
+                // the process fill its output buffer and choke, yay!
+                final InputStream stderr = proc.getErrorStream();
+                if (LaunchUtil.mustMonitorChildren()) {
+                    // close our window if it's around
+                    disposeContainer();
+                    _container = null;
+                    copyStream(stderr, System.err);
+                    log.info("Process exited: " + proc.waitFor());
+
+                } else {
+                    // spawn a daemon thread that will catch the early bits of stderr in case the
+                    // launch fails
+                    Thread t = new Thread() {
+                        @Override public void run () {
+                            copyStream(stderr, System.err);
+                        }
+                    };
+                    t.setDaemon(true);
+                    t.start();
+                }
+            }
+
+            // if we have a UI open and we haven't been around for at least 5 seconds (the default
+            // for min_show_seconds), don't stick a fork in ourselves straight away but give our
+            // lovely user a chance to see what we're doing
+            long uptime = System.currentTimeMillis() - _startup;
+            long minshow = _ifc.minShowSeconds * 1000L;
+            if (_container != null && uptime < minshow) {
+                try {
+                    Thread.sleep(minshow - uptime);
+                } catch (Exception e) {
+                }
+            }
+
+            // pump the percent up to 100%
+            setStatusAsync(null, 100, -1L, false);
+            exit(0);
+
+        } catch (Exception e) {
+            log.warning("launch() failed.", e);
+        }
+    }
+
+    /**
+     * Creates our user interface, which we avoid doing unless we actually have to update
+     * something. NOTE: this happens on the next UI tick, not immediately.
+     *
+     * @param reinit - if the interface should be reinitialized if it already exists.
+     */
+    protected void createInterfaceAsync (final boolean reinit)
+    {
+        if (_silent || (_container != null && !reinit)) {
+            return;
+        }
+
+        EventQueue.invokeLater(new Runnable() {
+            public void run () {
+                if (_container == null || reinit) {
+                    if (_container == null) {
+                        _container = createContainer();
+                    } else {
+                        _container.removeAll();
+                    }
+                    configureContainer();
+                    _layers = new JLayeredPane();
+                    _container.add(_layers, BorderLayout.CENTER);
+                    _patchNotes = new JButton(new AbstractAction(_msgs.getString("m.patch_notes")) {
+                        @Override public void actionPerformed (ActionEvent event) {
+                            showDocument(_ifc.patchNotesUrl);
+                        }
+                    });
+                    _patchNotes.setFont(StatusPanel.FONT);
+                    _layers.add(_patchNotes);
+                    _status = new StatusPanel(_msgs);
+                    _layers.add(_status);
+                    initInterface();
+                }
+                showContainer();
+            }
+        });
+    }
+
+    /**
+     * Initializes the interface with the current UpdateInterface and backgrounds.
+     */
+    protected void initInterface ()
+    {
+        RotatingBackgrounds newBackgrounds = getBackground();
+        if (_background == null || newBackgrounds.getNumImages() > 0) {
+            // Leave the old _background in place if there is an old one to leave in place
+            // and the new getdown.txt didn't yield any images.
+            _background = newBackgrounds;
+        }
+        _status.init(_ifc, _background, getProgressImage());
+        Dimension size = _status.getPreferredSize();
+        _status.setSize(size);
+        _layers.setPreferredSize(size);
+
+        _patchNotes.setBounds(_ifc.patchNotes.x, _ifc.patchNotes.y,
+                              _ifc.patchNotes.width, _ifc.patchNotes.height);
+        _patchNotes.setVisible(false);
+
+        // we were displaying progress while the UI wasn't up. Now that it is, whatever progress
+        // is left is scaled into a 0-100 DISPLAYED progress.
+        _uiDisplayPercent = _lastGlobalPercent;
+        _stepMinPercent = _lastGlobalPercent = 0;
+    }
+
+    protected RotatingBackgrounds getBackground ()
+    {
+        if (_ifc.rotatingBackgrounds != null) {
+            if (_ifc.backgroundImage != null) {
+                log.warning("ui.background_image and ui.rotating_background were both specified. " +
+                            "The rotating images are being used.");
+            }
+            return new RotatingBackgrounds(_ifc.rotatingBackgrounds, _ifc.errorBackground,
+                Getdown.this);
+        } else if (_ifc.backgroundImage != null) {
+            return new RotatingBackgrounds(loadImage(_ifc.backgroundImage));
+        } else {
+            return new RotatingBackgrounds();
+        }
+    }
+
+    protected Image getProgressImage ()
+    {
+        return loadImage(_ifc.progressImage);
+    }
+
+    protected void handleWindowClose ()
+    {
+        if (_dead) {
+            exit(0);
+        } else {
+            if (_abort == null) {
+                _abort = new AbortPanel(Getdown.this, _msgs);
+            }
+            _abort.pack();
+            SwingUtil.centerWindow(_abort);
+            _abort.setVisible(true);
+            _abort.setState(JFrame.NORMAL);
+            _abort.requestFocus();
+        }
+    }
+
+    /**
+     * Update the status to indicate getdown has failed for the reason in <code>message</code>.
+     */
+    protected void fail (String message)
+    {
+        _dead = true;
+        setStatusAsync(message, stepToGlobalPercent(0), -1L, true);
+    }
+
+    /**
+     * Set the current step, which will be used to globalize per-step percentages.
+     */
+    protected void setStep (Step step)
+    {
+        int finalPercent = -1;
+        for (Integer perc : _ifc.stepPercentages.get(step)) {
+            if (perc > _stepMaxPercent) {
+                finalPercent = perc;
+                break;
+            }
+        }
+        if (finalPercent == -1) {
+            // we've gone backwards and this step will be ignored
+            return;
+        }
+
+        _stepMaxPercent = finalPercent;
+        _stepMinPercent = _lastGlobalPercent;
+    }
+
+    /**
+     * Convert a step percentage to the global percentage.
+     */
+    protected int stepToGlobalPercent (int percent)
+    {
+        int adjustedMaxPercent =
+            ((_stepMaxPercent - _uiDisplayPercent) * 100) / (100 - _uiDisplayPercent);
+        _lastGlobalPercent = Math.max(_lastGlobalPercent,
+            _stepMinPercent + (percent * (adjustedMaxPercent - _stepMinPercent)) / 100);
+        return _lastGlobalPercent;
+    }
+
+    /**
+     * Updates the status. NOTE: this happens on the next UI tick, not immediately.
+     */
+    protected void setStatusAsync (final String message, final int percent, final long remaining,
+                                   boolean createUI)
+    {
+        if (_status == null && createUI) {
+            createInterfaceAsync(false);
+        }
+
+        EventQueue.invokeLater(new Runnable() {
+            public void run () {
+                if (_status == null) {
+                    if (message != null) {
+                        log.info("Dropping status '" + message + "'.");
+                    }
+                    return;
+                }
+                if (message != null) {
+                    _status.setStatus(message, _dead);
+                }
+                if (_dead) {
+                    _status.setProgress(0, -1L);
+                } else if (percent >= 0) {
+                    _status.setProgress(percent, remaining);
+                }
+            }
+        });
+    }
+
+    protected void reportTrackingEvent (String event, int progress)
+    {
+        if (!_enableTracking) {
+            return;
+
+        } else if (progress > 0) {
+            // we need to make sure we do the right thing if we skip over progress levels
+            do {
+                URL url = _app.getTrackingProgressURL(++_reportedProgress);
+                if (url != null) {
+                    new ProgressReporter(url).start();
+                }
+            } while (_reportedProgress <= progress);
+
+        } else {
+            URL url = _app.getTrackingURL(event);
+            if (url != null) {
+                new ProgressReporter(url).start();
+            }
+        }
+    }
+
+    /**
+     * Creates the container in which our user interface will be displayed.
+     */
+    protected abstract Container createContainer ();
+
+    /**
+     * Configures the interface container based on the latest UI config.
+     */
+    protected abstract void configureContainer ();
+
+    /**
+     * Shows the container in which our user interface will be displayed.
+     */
+    protected abstract void showContainer ();
+
+    /**
+     * Disposes the container in which we have our user interface.
+     */
+    protected abstract void disposeContainer ();
+
+    /**
+     * If this method returns true we will run the application in the same JVM, otherwise we will
+     * fork off a new JVM. Some options are not supported if we do not fork off a new JVM.
+     */
+    protected boolean invokeDirect ()
+    {
+        return SysProps.direct();
+    }
+
+    /**
+     * Requests to show the document at the specified URL in a new window.
+     */
+    protected abstract void showDocument (String url);
+
+    /**
+     * Requests that Getdown exit.
+     */
+    protected abstract void exit (int exitCode);
+
+    /**
+     * Copies the supplied stream from the specified input to the specified output. Used to copy
+     * our child processes stderr and stdout to our own stderr and stdout.
+     */
+    protected static void copyStream (InputStream in, PrintStream out)
+    {
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                out.print(line);
+                out.flush();
+            }
+        } catch (IOException ioe) {
+            log.warning("Failure copying", "in", in, "out", out, "error", ioe);
+        }
+    }
+
+    /** Used to fetch a progress report URL. */
+    protected class ProgressReporter extends Thread
+    {
+        public ProgressReporter (URL url) {
+            setDaemon(true);
+            _url = url;
+        }
+
+        @Override
+        public void run () {
+            try {
+                HttpURLConnection ucon = ConnectionUtil.openHttp(_app.proxy, _url, 0, 0);
+
+                // if we have a tracking cookie configured, configure the request with it
+                if (_app.getTrackingCookieName() != null &&
+                    _app.getTrackingCookieProperty() != null) {
+                    String val = System.getProperty(_app.getTrackingCookieProperty());
+                    if (val != null) {
+                        ucon.setRequestProperty("Cookie", _app.getTrackingCookieName() + "=" + val);
+                    }
+                }
+
+                // now request our tracking URL and ensure that we get a non-error response
+                ucon.connect();
+                try {
+                    if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                        log.warning("Failed to report tracking event",
+                            "url", _url, "rcode", ucon.getResponseCode());
+                    }
+                } finally {
+                    ucon.disconnect();
+                }
+
+            } catch (IOException ioe) {
+                log.warning("Failed to report tracking event", "url", _url, "error", ioe);
+            }
+        }
+
+        protected URL _url;
+    }
+
+    /** Used to pass progress on to our user interface. */
+    protected ProgressObserver _progobs = new ProgressObserver() {
+        public void progress (int percent) {
+            setStatusAsync(null, stepToGlobalPercent(percent), -1L, false);
+        }
+    };
+
+    protected Application _app;
+    protected Application.UpdateInterface _ifc = new Application.UpdateInterface(Config.EMPTY);
+
+    protected ResourceBundle _msgs;
+    protected Container _container;
+    protected JLayeredPane _layers;
+    protected StatusPanel _status;
+    protected JButton _patchNotes;
+    protected AbortPanel _abort;
+    protected RotatingBackgrounds _background;
+
+    protected boolean _dead;
+    protected boolean _silent;
+    protected boolean _launchInSilent;
+    protected boolean _noUpdate;
+    protected long _startup;
+
+    protected Set<Resource> _toInstallResources;
+    protected boolean _readyToInstall;
+
+    protected boolean _enableTracking = true;
+    protected int _reportedProgress = 0;
+
+    /** Number of minutes to wait after startup before beginning any real heavy lifting. */
+    protected int _delay;
+
+    protected int _stepMaxPercent;
+    protected int _stepMinPercent;
+    protected int _lastGlobalPercent;
+    protected int _uiDisplayPercent;
+
+    protected static final int MAX_LOOPS = 5;
+    protected static final long FALLBACK_CHECK_TIME = 1000L;
+    
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java
new file mode 100644 (file)
index 0000000..5e168ff
--- /dev/null
@@ -0,0 +1,287 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.EventQueue;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.KeyStroke;
+import javax.swing.WindowConstants;
+
+import com.install4j.api.launcher.StartupNotification;
+import com.samskivert.swing.util.SwingUtil;
+import com.threerings.getdown.data.Application;
+import com.threerings.getdown.data.EnvConfig;
+import com.threerings.getdown.data.SysProps;
+import com.threerings.getdown.util.LaunchUtil;
+import com.threerings.getdown.util.StringUtil;
+import static com.threerings.getdown.Log.log;
+
+/**
+ * The main application entry point for Getdown.
+ */
+public class GetdownApp
+{
+  public static String startupFilesParameterString = "";
+  /**
+   * The main entry point of the Getdown launcher application.
+   */
+  public static void main (String[] argv) {
+    try {
+      start(argv);
+    } catch (Exception e) {
+      log.warning("main() failed.", e);
+    }
+  }
+
+  /**
+   * Runs Getdown as an application, using the arguments supplie as {@code argv}.
+   * @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been
+   * called on it.
+   * @throws Exception if anything goes wrong starting Getdown.
+   */
+  public static Getdown start (String[] argv) throws Exception {
+    List<EnvConfig.Note> notes = new ArrayList<>();
+    EnvConfig envc = EnvConfig.create(argv, notes);
+    if (envc == null) {
+      if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message);
+      else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]");
+      System.exit(-1);
+    }
+
+    // pipe our output into a file in the application directory
+    if (!SysProps.noLogRedir()) {
+      File logFile = new File(envc.appDir, "launcher.log");
+      try {
+        PrintStream logOut = new PrintStream(
+                new BufferedOutputStream(new FileOutputStream(logFile)), true);
+        System.setOut(logOut);
+        System.setErr(logOut);
+      } catch (IOException ioe) {
+        log.warning("Unable to redirect output to '" + logFile + "': " + ioe);
+      }
+    }
+
+    // report any notes from reading our env config, and abort if necessary
+    boolean abort = false;
+    for (EnvConfig.Note note : notes) {
+      switch (note.level) {
+      case INFO: log.info(note.message); break;
+      case WARN: log.warning(note.message); break;
+      case ERROR: log.error(note.message); abort = true; break;
+      }
+    }
+    if (abort) System.exit(-1);
+
+    try
+    {
+      StartupNotification.registerStartupListener(
+              new StartupNotification.Listener() {
+                @Override
+                public void startupPerformed(String parameters)
+                { 
+                  log.warning("StartupNotification.Listener.startupPerformed: '"+parameters+"'");
+                  setStartupFilesParameterString(parameters);
+                }
+              });
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    
+    // record a few things for posterity
+    log.info("------------------ VM Info ------------------");
+    log.info("-- OS Name: " + System.getProperty("os.name"));
+    log.info("-- OS Arch: " + System.getProperty("os.arch"));
+    log.info("-- OS Vers: " + System.getProperty("os.version"));
+    log.info("-- Java Vers: " + System.getProperty("java.version"));
+    log.info("-- Java Home: " + System.getProperty("java.home"));
+    log.info("-- User Name: " + System.getProperty("user.name"));
+    log.info("-- User Home: " + System.getProperty("user.home"));
+    log.info("-- Cur dir: " + System.getProperty("user.dir"));
+    log.info("-- startupFilesParameterString: " + startupFilesParameterString);
+    log.info("---------------------------------------------");
+
+    Getdown app = new Getdown(envc) {
+      @Override
+      protected Container createContainer () {
+        // create our user interface, and display it
+        if (_frame == null) {
+          _frame = new JFrame("");
+          _frame.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing (WindowEvent evt) {
+              handleWindowClose();
+            }
+          });
+          // handle close on ESC
+          String cancelId = "Cancel"; // $NON-NLS-1$
+          _frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+                  KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelId);
+          _frame.getRootPane().getActionMap().put(cancelId, new AbstractAction() {
+            public void actionPerformed (ActionEvent e) {
+              handleWindowClose();
+            }
+          });
+          // this cannot be called in configureContainer as it is only allowed before the
+          // frame has been displayed for the first time
+          _frame.setUndecorated(_ifc.hideDecorations);
+          _frame.setResizable(false);
+        } else {
+          _frame.getContentPane().removeAll();
+        }
+        _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+        return _frame.getContentPane();
+      }
+
+      @Override
+      protected void configureContainer () {
+        if (_frame == null) return;
+
+        _frame.setTitle(_ifc.name);
+
+        try {
+          _frame.setBackground(new Color(_ifc.background, true));
+        } catch (Exception e) {
+          log.warning("Failed to set background", "bg", _ifc.background, e);
+        }
+
+        if (_ifc.iconImages != null) {
+          ArrayList<Image> icons = new ArrayList<>();
+          for (String path : _ifc.iconImages) {
+            Image img = loadImage(path);
+            if (img == null) {
+              log.warning("Error loading icon image", "path", path);
+            } else {
+              icons.add(img);
+            }
+          }
+          if (icons.isEmpty()) {
+            log.warning("Failed to load any icons", "iconImages", _ifc.iconImages);
+          } else {
+            _frame.setIconImages(icons);
+          }
+        }
+      }
+
+      @Override
+      protected void showContainer () {
+        if (_frame != null) {
+          _frame.pack();
+          SwingUtil.centerWindow(_frame);
+          _frame.setVisible(true);
+        }
+      }
+
+      @Override
+      protected void disposeContainer () {
+        if (_frame != null) {
+          _frame.dispose();
+          _frame = null;
+        }
+      }
+
+      @Override
+      protected void showDocument (String url) {
+        if (!StringUtil.couldBeValidUrl(url)) {
+          // command injection would be possible if we allowed e.g. spaces and double quotes
+          log.warning("Invalid document URL.", "url", url);
+          return;
+        }
+        String[] cmdarray;
+        if (LaunchUtil.isWindows()) {
+          String osName = System.getProperty("os.name", "");
+          if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
+            cmdarray = new String[] {
+                "command.com", "/c", "start", "\"" + url + "\"" };
+          } else {
+            cmdarray = new String[] {
+                "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" };
+          }
+        } else if (LaunchUtil.isMacOS()) {
+          cmdarray = new String[] { "open", url };
+        } else { // Linux, Solaris, etc.
+          cmdarray = new String[] { "firefox", url };
+        }
+        try {
+          Runtime.getRuntime().exec(cmdarray);
+        } catch (Exception e) {
+          log.warning("Failed to open browser.", "cmdarray", cmdarray, e);
+        }
+      }
+
+      @Override
+      protected void exit (int exitCode) {
+        // if we're running the app in the same JVM, don't call System.exit, but do
+        // make double sure that the download window is closed.
+        if (invokeDirect()) {
+          disposeContainer();
+        } else {
+          System.exit(exitCode);
+        }
+      }
+
+      @Override
+      protected void fail (String message) {
+        super.fail(message);
+        // super.fail causes the UI to be created (if needed) on the next UI tick, so we
+        // want to wait until that happens before we attempt to redecorate the window
+        EventQueue.invokeLater(new Runnable() {
+          @Override
+          public void run() {
+            // if the frame was set to be undecorated, make window decoration available
+            // to allow the user to close the window
+            if (_frame != null && _frame.isUndecorated()) {
+              _frame.dispose();
+              Color bg = _frame.getBackground();
+              if (bg != null && bg.getAlpha() < 255) {
+                // decorated windows do not allow alpha backgrounds
+                _frame.setBackground(
+                        new Color(bg.getRed(), bg.getGreen(), bg.getBlue()));
+              }
+              _frame.setUndecorated(false);
+              showContainer();
+            }
+          }
+        });
+      }
+
+      protected JFrame _frame;
+    };
+    
+    String startupFile = getStartupFilesParameterString();
+    if (!StringUtil.isBlank(startupFile)) {
+      Application.setStartupFilesFromParameterString(startupFile);
+    }
+    app.start();
+    return app;
+  }
+  
+  public static void setStartupFilesParameterString(String parameters) {
+    startupFilesParameterString = parameters;
+  }
+  
+  public static String getStartupFilesParameterString() {
+    return startupFilesParameterString;
+  }
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java
new file mode 100644 (file)
index 0000000..5ac7449
--- /dev/null
@@ -0,0 +1,20 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.io.IOException;
+
+/**
+ * Thrown when it's detected that multiple instances of the same getdown installer are running.
+ */
+public class MultipleGetdownRunning extends IOException
+{
+    public MultipleGetdownRunning ()
+    {
+        super("m.another_getdown_running");
+    }
+
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java
new file mode 100644 (file)
index 0000000..2178273
--- /dev/null
@@ -0,0 +1,195 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+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.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import com.samskivert.swing.GroupLayout;
+import com.samskivert.swing.Spacer;
+import com.samskivert.swing.VGroupLayout;
+import com.threerings.getdown.util.MessageUtil;
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Displays an interface with which the user can configure their proxy
+ * settings.
+ */
+public final class ProxyPanel extends JPanel implements ActionListener
+{
+    public ProxyPanel (Getdown getdown, ResourceBundle msgs)
+    {
+        _getdown = getdown;
+        _msgs = msgs;
+
+        setLayout(new VGroupLayout());
+        setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+        add(new SaneLabelField(get("m.configure_proxy")));
+        add(new Spacer(5, 5));
+
+        JPanel row = new JPanel(new GridLayout());
+        row.add(new SaneLabelField(get("m.proxy_host")), BorderLayout.WEST);
+        row.add(_host = new SaneTextField());
+        add(row);
+
+        row = new JPanel(new GridLayout());
+        row.add(new SaneLabelField(get("m.proxy_port")), BorderLayout.WEST);
+        row.add(_port = new SaneTextField());
+        add(row);
+
+        add(new Spacer(5, 5));
+
+        row = new JPanel(new GridLayout());
+        row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
+        _useAuth = new JCheckBox();
+        row.add(_useAuth);
+        add(row);
+
+        row = new JPanel(new GridLayout());
+        row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
+        _username = new SaneTextField();
+        _username.setEnabled(false);
+        row.add(_username);
+        add(row);
+
+        row = new JPanel(new GridLayout());
+        row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST);
+        _password = new SanePasswordField();
+        _password.setEnabled(false);
+        row.add(_password);
+        add(row);
+
+        _useAuth.addItemListener(new ItemListener() {
+            @Override public void itemStateChanged (ItemEvent event) {
+                boolean selected = (event.getStateChange() == ItemEvent.SELECTED);
+                _username.setEnabled(selected);
+                _password.setEnabled(selected);
+            }
+        });
+
+        add(new Spacer(5, 5));
+
+        row = GroupLayout.makeButtonBox(GroupLayout.CENTER);
+        JButton button;
+        row.add(button = new JButton(get("m.proxy_ok")));
+        button.setActionCommand("ok");
+        button.addActionListener(this);
+        row.add(button = new JButton(get("m.proxy_cancel")));
+        button.setActionCommand("cancel");
+        button.addActionListener(this);
+        add(row);
+    }
+
+    public void setProxy (String host, String port) {
+        if (host != null) {
+            _host.setText(host);
+        }
+        if (port != null) {
+            _port.setText(port);
+        }
+    }
+
+    // documentation inherited
+    @Override
+    public void addNotify ()
+    {
+        super.addNotify();
+        _host.requestFocusInWindow();
+    }
+
+    // documentation inherited
+    @Override
+    public Dimension getPreferredSize ()
+    {
+        // this is annoyingly hardcoded, but we can't just force the width
+        // or the JLabel will claim a bogus height thinking it can lay its
+        // text out all on one line which will booch the whole UI's
+        // preferred size
+        return new Dimension(500, 320);
+    }
+
+    // documentation inherited from interface
+    @Override
+    public void actionPerformed (ActionEvent e)
+    {
+        String cmd = e.getActionCommand();
+        if (cmd.equals("ok")) {
+            String user = null, pass = null;
+            if (_useAuth.isSelected()) {
+                user = _username.getText();
+                // we have to keep the proxy password around for every HTTP request, so having it
+                // in a char[] that gets zeroed out after use is not viable for this use case
+                pass = new String(_password.getPassword());
+            }
+            _getdown.configProxy(_host.getText(), _port.getText(), user, pass);
+        } else {
+            // they canceled, we're outta here
+            System.exit(0);
+        }
+    }
+
+    /** Used to look up localized messages. */
+    protected String get (String key)
+    {
+        // if this string is tainted, we don't translate it, instead we
+        // simply remove the taint character and return it to the caller
+        if (MessageUtil.isTainted(key)) {
+            return MessageUtil.untaint(key);
+        }
+        try {
+            return _msgs.getString(key);
+        } catch (MissingResourceException mre) {
+            log.warning("Missing translation message '" + key + "'.");
+            return key;
+        }
+    }
+
+    protected static class SaneLabelField extends JLabel {
+        public SaneLabelField(String message) { super(message); }
+        @Override public Dimension getPreferredSize () {
+            return clampWidth(super.getPreferredSize(), 200);
+        }
+    }
+    protected static class SaneTextField extends JTextField {
+        @Override public Dimension getPreferredSize () {
+            return clampWidth(super.getPreferredSize(), 150);
+        }
+    }
+    protected static class SanePasswordField extends JPasswordField {
+        @Override public Dimension getPreferredSize () {
+            return clampWidth(super.getPreferredSize(), 150);
+        }
+    }
+
+    protected static Dimension clampWidth (Dimension dim, int minWidth) {
+        dim.width = Math.max(dim.width, minWidth);
+        return dim;
+    }
+
+    protected Getdown _getdown;
+    protected ResourceBundle _msgs;
+
+    protected JTextField _host;
+    protected JTextField _port;
+    protected JCheckBox _useAuth;
+    protected JTextField _username;
+    protected JPasswordField _password;
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java
new file mode 100644 (file)
index 0000000..a36b5fa
--- /dev/null
@@ -0,0 +1,210 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.Authenticator;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import ca.beq.util.win32.registry.RegistryKey;
+import ca.beq.util.win32.registry.RegistryValue;
+import ca.beq.util.win32.registry.RootKey;
+
+import com.threerings.getdown.data.Application;
+import com.threerings.getdown.spi.ProxyAuth;
+import com.threerings.getdown.util.Config;
+import com.threerings.getdown.util.ConnectionUtil;
+import com.threerings.getdown.util.LaunchUtil;
+import com.threerings.getdown.util.StringUtil;
+
+import static com.threerings.getdown.Log.log;
+
+public class ProxyUtil {
+
+    public static boolean autoDetectProxy (Application app)
+    {
+        String host = null, port = null;
+
+        // check for a proxy configured via system properties
+        if (System.getProperty("https.proxyHost") != null) {
+            host = System.getProperty("https.proxyHost");
+            port = System.getProperty("https.proxyPort");
+        }
+        if (StringUtil.isBlank(host) && System.getProperty("http.proxyHost") != null) {
+            host = System.getProperty("http.proxyHost");
+            port = System.getProperty("http.proxyPort");
+        }
+
+        // check the Windows registry
+        if (StringUtil.isBlank(host) && LaunchUtil.isWindows()) {
+            try {
+                String rhost = null, rport = null;
+                boolean enabled = false;
+                RegistryKey.initialize();
+                RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
+                for (Iterator<?> iter = r.values(); iter.hasNext(); ) {
+                    RegistryValue value = (RegistryValue)iter.next();
+                    if (value.getName().equals("ProxyEnable")) {
+                        enabled = value.getStringValue().equals("1");
+                    }
+                    if (value.getName().equals("ProxyServer")) {
+                        String strval = value.getStringValue();
+                        int cidx = strval.indexOf(":");
+                        if (cidx != -1) {
+                            rport = strval.substring(cidx+1);
+                            strval = strval.substring(0, cidx);
+                        }
+                        rhost = strval;
+                    }
+                }
+                if (enabled) {
+                    host = rhost;
+                    port = rport;
+                } else {
+                    log.info("Detected no proxy settings in the registry.");
+                }
+            } catch (Throwable t) {
+                log.info("Failed to find proxy settings in Windows registry", "error", t);
+            }
+        }
+
+        // look for a proxy.txt file
+        if (StringUtil.isBlank(host)) {
+            String[] hostPort = loadProxy(app);
+            host = hostPort[0];
+            port = hostPort[1];
+        }
+
+        if (StringUtil.isBlank(host)) {
+            return false;
+        }
+
+        // yay, we found a proxy configuration, configure it in the app
+        initProxy(app, host, port, null, null);
+        return true;
+    }
+
+    public static boolean canLoadWithoutProxy (URL rurl)
+    {
+        log.info("Testing whether proxy is needed, via: " + rurl);
+        try {
+            // try to make a HEAD request for this URL (use short connect and read timeouts)
+            URLConnection conn = ConnectionUtil.open(Proxy.NO_PROXY, rurl, 5, 5);
+            if (conn instanceof HttpURLConnection) {
+                HttpURLConnection hcon = (HttpURLConnection)conn;
+                try {
+                    hcon.setRequestMethod("HEAD");
+                    hcon.connect();
+                    // make sure we got a satisfactory response code
+                    int rcode = hcon.getResponseCode();
+                    if (rcode == HttpURLConnection.HTTP_PROXY_AUTH ||
+                        rcode == HttpURLConnection.HTTP_FORBIDDEN) {
+                        log.warning("Got an 'HTTP credentials needed' response", "code", rcode);
+                    } else {
+                        return true;
+                    }
+                } finally {
+                    hcon.disconnect();
+                }
+            } else {
+                // if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy
+                return true;
+            }
+        } catch (IOException ioe) {
+            log.info("Failed to HEAD " + rurl + ": " + ioe);
+            log.info("We probably need a proxy, but auto-detection failed.");
+        }
+        return false;
+    }
+
+    public static void configProxy (Application app, String host, String port,
+                                    String username, String password) {
+        // save our proxy host and port in a local file
+        saveProxy(app, host, port);
+
+        // save our credentials via the SPI
+        if (!StringUtil.isBlank(username) && !StringUtil.isBlank(password)) {
+            ServiceLoader<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
+            Iterator<ProxyAuth> iterator = loader.iterator();
+            String appDir = app.getAppDir().getAbsolutePath();
+            while (iterator.hasNext()) {
+                iterator.next().saveCredentials(appDir, username, password);
+            }
+        }
+
+        // also configure them in the app
+        initProxy(app, host, port, username, password);
+    }
+
+    public static String[] loadProxy (Application app) {
+        File pfile = app.getLocalPath("proxy.txt");
+        if (pfile.exists()) {
+            try {
+                Config pconf = Config.parseConfig(pfile, Config.createOpts(false));
+                return new String[] { pconf.getString("host"), pconf.getString("port") };
+            } catch (IOException ioe) {
+                log.warning("Failed to read '" + pfile + "': " + ioe);
+            }
+        }
+        return new String[] { null, null};
+    }
+
+    public static void saveProxy (Application app, String host, String port) {
+        File pfile = app.getLocalPath("proxy.txt");
+        try (PrintStream pout = new PrintStream(new FileOutputStream(pfile))) {
+            if (!StringUtil.isBlank(host)) {
+                pout.println("host = " + host);
+            }
+            if (!StringUtil.isBlank(port)) {
+                pout.println("port = " + port);
+            }
+        } catch (IOException ioe) {
+            log.warning("Error creating proxy file '" + pfile + "': " + ioe);
+        }
+    }
+
+    public static void initProxy (Application app, String host, String port,
+                                  String username, String password)
+    {
+        // check whether we have saved proxy credentials
+        String appDir = app.getAppDir().getAbsolutePath();
+        ServiceLoader<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
+        Iterator<ProxyAuth> iter = loader.iterator();
+        ProxyAuth.Credentials creds = iter.hasNext() ? iter.next().loadCredentials(appDir) : null;
+        if (creds != null) {
+            username = creds.username;
+            password = creds.password;
+        }
+        boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password);
+
+        int pport = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port);
+        log.info("Using proxy", "host", host, "port", pport, "haveCreds", haveCreds);
+        app.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pport));
+
+        if (haveCreds) {
+            final String fuser = username;
+            final char[] fpass = password.toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override protected PasswordAuthentication getPasswordAuthentication () {
+                    return new PasswordAuthentication(fuser, fpass);
+                }
+            });
+        }
+    }
+
+    protected static final String PROXY_REGISTRY =
+        "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java
new file mode 100644 (file)
index 0000000..d3aa2bd
--- /dev/null
@@ -0,0 +1,132 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.Image;
+import java.util.List;
+
+import static com.threerings.getdown.Log.log;
+
+public final class RotatingBackgrounds
+{
+    public interface ImageLoader {
+        /** Loads and returns the image with the supplied path. */
+        public Image loadImage (String path);
+    }
+
+    /**
+     * Creates a placeholder if there are no images. Just returns null from getImage every time.
+     */
+    public RotatingBackgrounds ()
+    {
+        makeEmpty();
+    }
+
+    /** Creates a single image background. */
+    public RotatingBackgrounds (Image background)
+    {
+        percentages = new int[] { 0 };
+        minDisplayTime = new int[] { 0 };
+        images = new Image[] { background };
+        errorImage = images[0];
+    }
+
+    /**
+     * Create a sequence of images to be rotated through from <code>backgrounds</code>.
+     *
+     * Each String in backgrounds should be the path to the image, a semicolon, and the minimum
+     * amount of time to display the image in seconds. Each image will be active for an equal
+     * percentage of the download process, unless one hasn't been active for its minimum display
+     * time when the next should be shown. In that case, it's left up until its been there for its
+     * minimum display time and then the next one gets to come up.
+     */
+    public RotatingBackgrounds (List<String> backgrounds, String errorBackground, ImageLoader loader)
+    {
+        percentages = new int[backgrounds.size()];
+        minDisplayTime = new int[backgrounds.size()];
+        images = new Image[backgrounds.size()];
+        for (int ii = 0; ii < backgrounds.size(); ii++) {
+            String background = backgrounds.get(ii);
+            String[] pieces = background.split(";");
+            if (pieces.length != 2) {
+                log.warning("Unable to parse background image '" + background + "'");
+                makeEmpty();
+                return;
+            }
+            images[ii] = loader.loadImage(pieces[0]);
+            try {
+                minDisplayTime[ii] = Integer.parseInt(pieces[1]);
+            } catch (NumberFormatException e) {
+                log.warning("Unable to parse background image display time '" + background + "'");
+                makeEmpty();
+                return;
+            }
+            percentages[ii] = (int)((ii/(float)backgrounds.size()) * 100);
+        }
+        if (errorBackground == null) {
+            errorImage = images[0];
+        } else {
+            errorImage = loader.loadImage(errorBackground);
+        }
+    }
+
+    /**
+     * @return the image to display at the given progress or null if there aren't any.
+     */
+    public Image getImage (int progress)
+    {
+        if (images.length == 0) {
+            return null;
+        }
+        long now = System.currentTimeMillis();
+        if (current != images.length - 1
+            && (current == -1 || (progress >= percentages[current + 1] &&
+                    (now - currentDisplayStart) / 1000 > minDisplayTime[current]))) {
+            current++;
+            currentDisplayStart = now;
+        }
+        return images[current];
+    }
+
+    /**
+     * Returns the image to display if an error has caused getdown to fail.
+     */
+    public Image getErrorImage ()
+    {
+        return errorImage;
+    }
+
+    /**
+     * @return the number of images in this RotatingBackgrounds
+     */
+    public int getNumImages() {
+        return images.length;
+    }
+
+    protected void makeEmpty ()
+    {
+        percentages = new int[] {};
+        minDisplayTime = new int[] {};
+        images = new Image[] {};
+    }
+
+    /** Time at which the currently displayed image was first displayed in millis. */
+    protected long currentDisplayStart;
+
+    /** The index of the currently displayed image or -1 if we haven't displayed any. */
+    protected int current = -1;
+
+    protected Image[] images;
+
+    /** The image to display if getdown has failed due to an error. */
+    protected Image errorImage;
+
+    /** Percentage at which each image should be displayed. */
+    protected int[] percentages;
+
+    /** Time to show each image in seconds. */
+    protected int[] minDisplayTime;
+}
diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java
new file mode 100644 (file)
index 0000000..99f44ca
--- /dev/null
@@ -0,0 +1,396 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.launcher;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.ImageObserver;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.JComponent;
+import javax.swing.Timer;
+
+import com.samskivert.swing.Label;
+import com.samskivert.swing.LabelStyleConstants;
+import com.samskivert.swing.util.SwingUtil;
+import com.samskivert.util.Throttle;
+
+import com.threerings.getdown.data.Application.UpdateInterface;
+import com.threerings.getdown.util.MessageUtil;
+import com.threerings.getdown.util.Rectangle;
+import com.threerings.getdown.util.StringUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Displays download and patching status.
+ */
+public final class StatusPanel extends JComponent
+    implements ImageObserver
+{
+    public StatusPanel (ResourceBundle msgs)
+    {
+        _msgs = msgs;
+
+        // Add a bit of "throbbing" to the display by updating the number of dots displayed after
+        // our status. This lets users know things are still working.
+        _timer = new Timer(1000,
+            new ActionListener() {
+                public void actionPerformed (ActionEvent event) {
+                    if (_status != null && !_displayError) {
+                        _statusDots = (_statusDots % 3) + 1; // 1, 2, 3, 1, 2, 3, etc.
+                        updateStatusLabel();
+                    }
+                }
+            });
+    }
+
+    public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg)
+    {
+        _ifc = ifc;
+        _bg = bg;
+        Image img = _bg.getImage(_progress);
+        int width = img == null ? -1 : img.getWidth(this);
+        int height = img == null ? -1 : img.getHeight(this);
+        if (width == -1 || height == -1) {
+            Rectangle bounds = ifc.progress.union(ifc.status);
+            // assume the x inset defines the frame padding; add it on the left, right, and bottom
+            _psize = new Dimension(bounds.x + bounds.width + bounds.x,
+                                   bounds.y + bounds.height + bounds.x);
+        } else {
+            _psize = new Dimension(width, height);
+        }
+        _barimg = barimg;
+        invalidate();
+    }
+
+    @Override
+    public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height)
+    {
+        boolean updated = false;
+        if ((infoflags & WIDTH) != 0) {
+            _psize.width = width;
+            updated = true;
+        }
+        if ((infoflags & HEIGHT) != 0) {
+            _psize.height = height;
+            updated = true;
+        }
+        if (updated) {
+            invalidate();
+            setSize(_psize);
+            getParent().setSize(_psize);
+        }
+        return (infoflags & ALLBITS) == 0;
+    }
+
+    /**
+     * Adjusts the progress display to the specified percentage.
+     */
+    public void setProgress (int percent, long remaining)
+    {
+        boolean needsRepaint = false;
+
+        // maybe update the progress label
+        if (_progress != percent) {
+            _progress = percent;
+            if (!_ifc.hideProgressText) {
+                String msg = MessageFormat.format(get("m.complete"), percent);
+                _newplab = createLabel(msg, new Color(_ifc.progressText, true));
+            }
+            needsRepaint = true;
+        }
+
+        // maybe update the remaining label
+        if (remaining > 1) {
+            // skip this estimate if it's been less than a second since our last one came in
+            if (!_rthrottle.throttleOp()) {
+                _remain[_ridx++%_remain.length] = remaining;
+            }
+
+            // smooth the remaining time by taking the trailing average of the last four values
+            remaining = 0;
+            int values = Math.min(_ridx, _remain.length);
+            for (int ii = 0; ii < values; ii++) {
+                remaining += _remain[ii];
+            }
+            remaining /= values;
+
+            if (!_ifc.hideProgressText) {
+                // now compute our display value
+                int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60);
+                String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds;
+                String msg = MessageFormat.format(get("m.remain"), remstr);
+                _newrlab = createLabel(msg, new Color(_ifc.statusText, true));
+            }
+            needsRepaint = true;
+
+        } else if (_rlabel != null || _newrlab != null) {
+            _rthrottle = new Throttle(1, 1000);
+            _ridx = 0;
+            _newrlab = _rlabel = null;
+            needsRepaint = true;
+        }
+
+        if (needsRepaint) {
+            repaint();
+        }
+    }
+
+    /**
+     * Displays the specified status string.
+     */
+    public void setStatus (String status, boolean displayError)
+    {
+        _status = xlate(status);
+        _displayError = displayError;
+        updateStatusLabel();
+    }
+
+    /**
+     * Stop the throbbing.
+     */
+    public void stopThrob ()
+    {
+        _timer.stop();
+        _statusDots = 3;
+        updateStatusLabel();
+    }
+
+    @Override
+    public void addNotify ()
+    {
+        super.addNotify();
+        _timer.start();
+    }
+
+    @Override
+    public void removeNotify ()
+    {
+        _timer.stop();
+        super.removeNotify();
+    }
+
+    // documentation inherited
+    @Override
+    public void paintComponent (Graphics g)
+    {
+        super.paintComponent(g);
+        Graphics2D gfx = (Graphics2D)g;
+
+        // attempt to draw a background image...
+        Image img;
+        if (_displayError) {
+            img = _bg.getErrorImage();
+        } else {
+            img = _bg.getImage(_progress);
+        }
+        if (img != null) {
+            gfx.drawImage(img, 0, 0, this);
+        }
+
+        Object oalias = SwingUtil.activateAntiAliasing(gfx);
+
+        // if we have new labels; lay them out
+        if (_newlab != null) {
+            _newlab.layout(gfx);
+            _label = _newlab;
+            _newlab = null;
+        }
+        if (_newplab != null) {
+            _newplab.layout(gfx);
+            _plabel = _newplab;
+            _newplab = null;
+        }
+        if (_newrlab != null) {
+            _newrlab.layout(gfx);
+            _rlabel = _newrlab;
+            _newrlab = null;
+        }
+
+        if (_barimg != null) {
+            gfx.setClip(_ifc.progress.x, _ifc.progress.y,
+                        _progress * _ifc.progress.width / 100,
+                        _ifc.progress.height);
+            gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null);
+            gfx.setClip(null);
+        } else {
+            gfx.setColor(new Color(_ifc.progressBar, true));
+            gfx.fillRect(_ifc.progress.x, _ifc.progress.y,
+                         _progress * _ifc.progress.width / 100,
+                         _ifc.progress.height);
+        }
+
+        if (_plabel != null) {
+            int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2;
+            int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2;
+            _plabel.render(gfx, _ifc.progress.x + xmarg, _ifc.progress.y + ymarg);
+        }
+
+        if (_label != null) {
+            _label.render(gfx, _ifc.status.x, getStatusY(_label));
+        }
+
+        if (_rlabel != null) {
+            // put the remaining label at the end of the status area. This could be dangerous
+            // but I think the only time we would display it is with small statuses.
+            int x = _ifc.status.x + _ifc.status.width - _rlabel.getSize().width;
+            _rlabel.render(gfx, x, getStatusY(_rlabel));
+        }
+
+        SwingUtil.restoreAntiAliasing(gfx, oalias);
+    }
+
+    // documentation inherited
+    @Override
+    public Dimension getPreferredSize ()
+    {
+        return _psize;
+    }
+
+    /**
+     * Update the status label.
+     */
+    protected void updateStatusLabel ()
+    {
+        String status = _status;
+        if (!_displayError) {
+            for (int ii = 0; ii < _statusDots; ii++) {
+                status += " .";
+            }
+        }
+        _newlab = createLabel(status, new Color(_ifc.statusText, true));
+        // set the width of the label to the width specified
+        int width = _ifc.status.width;
+        if (width == 0) {
+            // unless we had trouble reading that width, in which case use the entire window
+            width = getWidth();
+        }
+        // but the window itself might not be initialized and have a width of 0
+        if (width > 0) {
+            _newlab.setTargetWidth(width);
+        }
+        repaint();
+    }
+
+    /**
+     * Get the y coordinate of a label in the status area.
+     */
+    protected int getStatusY (Label label)
+    {
+        // if the status region is higher than the progress region, we
+        // want to align the label with the bottom of its region
+        // rather than the top
+        if (_ifc.status.y > _ifc.progress.y) {
+            return _ifc.status.y;
+        }
+        return _ifc.status.y + (_ifc.status.height - label.getSize().height);
+    }
+
+    /**
+     * Create a label, taking care of adding the shadow if needed.
+     */
+    protected Label createLabel (String text, Color color)
+    {
+        Label label = new Label(text, color, FONT);
+        if (_ifc.textShadow != 0) {
+            label.setAlternateColor(new Color(_ifc.textShadow, true));
+            label.setStyle(LabelStyleConstants.SHADOW);
+        }
+        return label;
+    }
+
+    /** Used by {@link #setStatus}. */
+    protected String xlate (String compoundKey)
+    {
+        // to be more efficient about creating unnecessary objects, we
+        // do some checking before splitting
+        int tidx = compoundKey.indexOf('|');
+        if (tidx == -1) {
+            return get(compoundKey);
+
+        } else {
+            String key = compoundKey.substring(0, tidx);
+            String argstr = compoundKey.substring(tidx+1);
+            String[] args = argstr.split("\\|");
+            // unescape and translate the arguments
+            for (int i = 0; i < args.length; i++) {
+                // if the argument is tainted, do no further translation
+                // (it might contain |s or other fun stuff)
+                if (MessageUtil.isTainted(args[i])) {
+                    args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i]));
+                } else {
+                    args[i] = xlate(MessageUtil.unescape(args[i]));
+                }
+            }
+            return get(key, args);
+        }
+    }
+
+    /** Used by {@link #setStatus}. */
+    protected String get (String key, String[] args)
+    {
+        String msg = get(key);
+        if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
+        return key + String.valueOf(Arrays.asList(args));
+    }
+
+    /** Used by {@link #setStatus}, and {@link #setProgress}. */
+    protected String get (String key)
+    {
+        // if we have no _msgs that means we're probably recovering from a
+        // failure to load the translation messages in the first place, so
+        // just give them their key back because it's probably an english
+        // string; whee!
+        if (_msgs == null) {
+            return key;
+        }
+
+        // if this string is tainted, we don't translate it, instead we
+        // simply remove the taint character and return it to the caller
+        if (MessageUtil.isTainted(key)) {
+            return MessageUtil.untaint(key);
+        }
+        try {
+            return _msgs.getString(key);
+        } catch (MissingResourceException mre) {
+            log.warning("Missing translation message '" + key + "'.");
+            return key;
+        }
+    }
+
+    protected Image _barimg;
+    protected RotatingBackgrounds _bg;
+    protected Dimension _psize;
+
+    protected ResourceBundle _msgs;
+
+    protected int _progress = -1;
+    protected String _status;
+    protected int _statusDots = 1;
+    protected boolean _displayError;
+    protected Label _label, _newlab;
+    protected Label _plabel, _newplab;
+    protected Label _rlabel, _newrlab;
+
+    protected UpdateInterface _ifc;
+    protected Timer _timer;
+
+    protected long[] _remain = new long[4];
+    protected int _ridx;
+    protected Throttle _rthrottle = new Throttle(1, 1000L);
+
+    protected static final Font FONT = new Font("SansSerif", Font.BOLD, 12);
+}
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties
new file mode 100644 (file)
index 0000000..19b2999
--- /dev/null
@@ -0,0 +1,110 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Abort installation?
+m.abort_confirm = <html>Are you sure you want to stop installation? \
+  You can resume at a later time by running the application again.</html>
+m.abort_ok = Quit
+m.abort_cancel = Continue installation
+
+m.detecting_proxy = Trying to auto-detect proxy settings
+
+m.configure_proxy = <html>We were unable to connect to the application server to download data. \
+  <p> Please make sure that no virus scanner or firewall is blocking network communicaton with \
+  the server. \
+  <p> Your computer may access the Internet through a proxy and we were unable to automatically \
+  detect your proxy settings. If you know your proxy settings, you can enter them below.</html>
+
+m.proxy_extra = <html>If you are sure that you don't use a proxy then \
+  perhaps there is a temporary Internet outage that is preventing us from \
+  communicating with the servers. In this case, you can cancel and try \
+  installing again later.</html>
+
+m.proxy_host = Proxy IP
+m.proxy_port = Proxy port
+m.proxy_username = Username
+m.proxy_password = Password
+m.proxy_auth_required = Authentication required
+m.proxy_ok = OK
+m.proxy_cancel = Cancel
+
+m.downloading_java = Downloading Java Virtual Machine
+m.unpacking_java = Unpacking Java Virtual Machine
+
+m.resolving = Resolving downloads
+m.downloading = Downloading data
+m.failure = Download failed: {0}
+
+m.checking = Checking for update
+m.validating = Validating
+m.patching = Patching
+m.launching = Launching
+
+m.patch_notes = Patch Notes
+m.play_again = Play Again
+
+m.complete = {0}% complete
+m.remain = {0} remaining
+
+m.updating_metadata = Downloading control files
+
+m.init_failed = Our configuration file is missing or corrupt. Attempting \
+  to download a new copy...
+
+m.java_download_failed = We were unable to automatically download the \
+  necessary version of Java for your computer.\n\n\
+  Please go to www.java.com and download the latest version of \
+  Java, then try running the application again.
+
+m.java_unpack_failed = We were unable to unpack an updated version of \
+  Java. Please make sure you have at least 100 MB of free space on your \
+  harddrive and try running the application again.\n\n\
+  If that does not solve the problem, go to www.java.com and download and \
+  install the latest version of Java and try again.
+
+m.unable_to_repair = We were unable to download the necessary files after \
+  five attempts. You can try running the application again, but if it \
+  fails you may need to uninstall and reinstall.
+
+m.unknown_error = The application has failed to launch due to some strange \
+  error from which we could not recover. Please visit\n{0} for information on \
+  how to recover.
+m.init_error = The application has failed to launch due to the following \
+  error:\n{0}\n\nPlease visit\n{1} for \
+  information on how to handle such problems.
+
+m.readonly_error = The directory in which this application is installed: \
+  \n{0}\nis read-only. Please install the application into a directory where \
+  you have write access.
+
+m.missing_resource = The application has failed to launch due to a missing \
+  resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \
+  problems.
+
+m.insufficient_permissions_error = You did not accept this application's \
+ digital signature. If you want to run the application, you will need to accept \
+ its digital signature.\n\nTo do so, you will need to quit your web browser, \
+ restart it, and return to this web page to relaunch the application. When the \
+ security dialog is shown, click the button to accept the digital signature \
+ and grant this application the privileges it needs to run.
+
+m.corrupt_digest_signature_error = We couldn't verify the application's digital \
+ signature.\nPlease check that you are launching the application from\nthe \
+ correct website.
+
+m.default_install_error = the support section of the website
+
+m.another_getdown_running = Multiple instances of this application's \
+ installer are running. This one will stop and let another complete.
+
+m.applet_stopped = Getdown's applet was told to stop working.
+
+# application/digest errors
+m.missing_appbase = The configuration file is missing the 'appbase'.
+m.invalid_version = The configuration file specifies an invalid version.
+m.invalid_appbase = The configuration file specifies an invalid 'appbase'.
+m.missing_class = The configuration file is missing the application class.
+m.missing_code = The configuration file specifies no code resources.
+m.invalid_digest_file = The digest file is invalid.
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties
new file mode 100644 (file)
index 0000000..8e36835
--- /dev/null
@@ -0,0 +1,116 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Installation abbrechen?
+m.abort_confirm = <html>Bist du sicher, dass du die Installation abbrechen \
+m\u00f6chtest? \
+  Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \
+ausf\u00fchrst.</html>
+m.abort_ok = Beenden
+m.abort_cancel = Installation fortsetzen
+
+m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln
+
+m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \
+  <p>Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \
+  im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \
+  mit dem Server blockiert.<br> \
+  Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \
+  stehenden Feldern und klicken sie auf OK.</html>
+
+m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
+  an Ihren Administrator.</html>
+
+m.proxy_host = Proxy-Adresse
+m.proxy_port = Proxy-Port
+m.proxy_username = Benutzername
+m.proxy_password = Passwort
+m.proxy_auth_required = Authentisierung erforderlich
+m.proxy_ok = OK
+m.proxy_cancel = Abbrechen
+
+m.downloading_java = Lade Java Virtual Machine herunter
+m.unpacking_java = Entpacke Java Virtual Machine
+
+m.resolving = Bereite Download vor
+m.downloading = Lade Daten herunter
+m.failure = Download fehlgeschlagen: {0}
+
+m.checking = Suche nach Updates
+m.validating = Validiere Download
+m.patching = Patche
+m.launching = Starte
+
+m.patch_notes = Patchnotes
+
+m.complete = {0}% abgeschlossen
+m.remain = {0} \u00fcbrig
+
+m.updating_metadata = Lade Steuerungsdateien herunter
+
+m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \
+Versuche, eine neue Kopie herunterzuladen...
+
+m.java_download_failed = Wir konnten die notwendige Javaversion f\u00fcr deinen \
+Computer nicht automatisch herunterladen. \n\n \
+Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die \
+Anwendung erneut starten.
+
+m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \
+entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \
+Festplatte frei sind und versuche dann die Anwendung erneut zu \
+starten.\n\n\ \
+Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \
+Javaversion herunterladen und installieren und dann erneut versuchen.
+
+m.unable_to_repair = Wir konnten die notwendigen Dateien nach 5 Versuchen \
+nicht herunterladen. Du kannst versuchen, die Anwendung erneut zu starten, \
+aber wenn dies erneut fehlschl\u00e4gt, musst du die Anwendung deinstallieren \
+und erneut installieren.
+
+m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \
+nicht gestartet werden. Bitte auf \n{0} weiterlesen.
+
+m.init_error = Die Anwendung konnte wegen folgendem Fehler nicht gestartet \
+werden:\n{0}\n\n Bitte auf \n{1} weiterlesen, um zu erfahren, wie bei \
+solchen Problemen vorzugehen ist.
+
+m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \
+ \n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \
+Schreibzugriff installieren.
+
+m.missing_resource = Die Anwendung konnte nicht gestartet werden, da die \
+folgende Quelle nicht gefunden wurde:\n{0}\n\n\ Bitte auf \n{1} \
+weiterlesen, um zu erfahren, wie bei solchen Problemen vorzugehen ist.
+
+m.insufficient_permissions_error = Du hast die digitale Signatur dieser \
+Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, \
+musst du ihre digitale Signatur akzeptieren. \n\Um das zu tun, musst du \
+deinen Browser beenden, neu starten und erneut die Anwendung von dieser \
+Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die \
+digitale Signatur akzeptieren, um der Anwendung die n\u00f6tigen Rechte zu \
+geben, die sie braucht, um zu laufen.
+
+m.corrupt_digest_signature_error = Wir konnten die digitale Signatur \
+dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung \
+von der richtigen Webseite aus startest.
+
+m.default_install_error = der Support-Webseite
+
+m.another_getdown_running = Diese Installationsanwendung l\u00e4uft in mehreren \
+Instanzen. Diese Instanz wird sich beenden und eine andere Instanz den \
+Vorgang erledigen lassen.
+
+m.applet_stopped = Die Anwendung wurde beendet.
+
+
+# application/digest errors
+m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'.
+m.invalid_version = In der Konfigurationsdatei steht die falsche Version.
+m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'.
+m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse.
+m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen.
+m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig.
+
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties
new file mode 100644 (file)
index 0000000..609b025
--- /dev/null
@@ -0,0 +1,115 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u00bfCancelar la instalaci\u00f3n?
+m.abort_confirm = <html>\u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \
+  Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n.</html>
+m.abort_ok = Cancelar
+m.abort_cancel = Continuar la instalaci\u00f3n
+
+m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy
+
+m.configure_proxy = <html>No ha sido posible conectar con nuestros servidores para \
+  descargar los datos del juego. \
+  <ul><li> Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \
+  de bloquear <code>javaw.exe</code> no podemos descargar el juego. Necesitar\u00e1s \
+  permitir que <code>javaw.exe</code> tenga acceso al Internet. Puedes intentar \
+  correr el juego de nuevo, pero es posible que debas dar permisos a javaw.exe en la \
+  configuraci\u00f3n de tu cortafuegos ( Inicio -> Panel de control -> Firewall de Windows ).</ul> \
+  <p> Es posible que tu computadora tenga acceso al Internet por medio de un proxy por lo que \
+  no ha sido posible detectar autom\u00e1ticamente tu configuraci\u00f3n. Si conoces tu \
+  configuraci\u00f3n proxy, puedes anotarla abajo.</html>
+
+m.proxy_extra = <html>Si est\u00e1s seguro de que no tienes un proxy entonces \
+  tal vez exista un falla temporal en el Internet que est\u00e1 evitando que podamos \
+  comunicarnos con los servidores. En este caso, puedes cancelar e intentar \
+  instalarla de nuevo m\u00e1s tarde.</html>
+
+m.proxy_host = IP proxy
+m.proxy_port = Puerto proxy
+m.proxy_username = Nombre de usuario
+m.proxy_password = Contrase\u00f1a
+m.proxy_auth_required = Autenticacion requerida
+m.proxy_ok = OK
+m.proxy_cancel = Cancelar
+
+m.downloading_java = Descargando Java Virtual Machine
+m.unpacking_java = Desempacando Java Virtual Machine
+
+m.resolving = Resolviendo descarga
+m.downloading = Descargando datos
+m.failure = Descarga fallida: {0}
+
+m.checking = Buscando actualizaciones
+m.validating = Validando
+m.patching = Parchando
+m.launching = Lanzando
+
+m.patch_notes = Notas del parche
+
+m.complete = {0}% completado
+m.remain = {0} restante
+
+m.updating_metadata = Descargando los archivos de control
+
+m.init_failed = Un archivo de configuraci\u00f3n est\u00e1 faltante o est\u00e1 corrupto. Intentando \
+  descargar una nueva copia...
+
+m.java_download_failed = No ha sido posible descargar autom\u00e1ticamente la \
+  versi\u00f3n de Java necesaria para tu computadora.\n\n\
+  Por favor ve a www.java.com y descarga la \u00faltima versi\u00f3n de \
+  Java, despu\u00e9s intenta correr de nuevo la aplicaci\u00f3n.
+
+m.java_unpack_failed = No ha sido posible desempacar una versi\u00f3n actualizada de \
+  Java. Por favor aseg\u00farate de tener al menos 100 MB de espacio libre en tu \
+  disco duro e intenta correr de nuevo la aplicaci\u00f3n.\n\n\
+  Si eso no soluciona el problema, ve a www.java.com y descarga e \
+  instala la \u00faltima versi\u00f3n de Java e intenta de nuevo.
+
+m.unable_to_repair = No ha sido posible descargar los archivos necesarios despu\u00e9s de \
+  cinco intentos. Puedes intentar correr de nuevo la aplicaci\u00f3n, pero si falla \
+  de nuevo podr\u00edas necesitar desinstalar y reinstalar.
+
+m.unknown_error = La aplicaci\u00f3n no ha podido iniciar debido a un extra\u00f1o \
+  error del que no se pudo recobrar. Por favor visita\n{0} para ver informaci\u00f3n acerca \
+  de como recuperarla.
+m.init_error = La aplicaci\u00f3n no ha podido iniciar debido al siguiente \
+  error:\n{0}\n\nPor favor visita\n{1} para \
+  ver informaci\u00f3n acerca de como manejar ese tipo de problemas.
+
+m.readonly_error = El directorio en el que esta aplicaci\u00f3n est\u00e1 instalada: \
+  \n{0}\nes solo lectura. Por favor instala la aplicaci\u00f3n en un directorio en el cual \
+  tengas acceso de escritura.
+
+m.missing_resource = La aplicaci\u00f3n no ha podido iniciar debido a un recurso \
+  faltante:\n{0}\n\nPor favor visita\n{1} para informaci\u00f3n acerca de como solucionar \
+  estos problemas.
+
+m.insufficient_permissions_error = No aceptaste la firma digital de \
+ esta aplicaci\u00f3n. Si quieres correr la aplicaci\u00f3n, necesitas aceptar \
+ su firma digital.\n\nPara hacerlo, necesitas cerrar tu navegador, \
+ reiniciarlo, y regresar a esta p\u00e1gina web para reiniciar la aplicaci\u00f3n. Cuando se muestre \
+ el di\u00e1logo de seguridad, haz clic en el bot\u00f3n para aceptar la firmar digital \
+ y otorgar a esta aplicaci\u00f3n los privilegios que necesita para correr.
+
+m.corrupt_digest_signature_error = No pudimos verificar la firma digital \
+ de la aplicaci\u00f3n.\nPor favor revisa que est\u00e9s lanzando la aplicaci\u00f3n desde\nel \
+ sitio web correcto.
+
+m.default_install_error = la secci\u00f3n de asistencia de este sitio web
+
+m.another_getdown_running = Est\u00e1n corriendo m\u00faltiples instancias de \
+ este instalador.  Este se detendr\u00e1 para permitir que otra contin\u00fae.
+
+m.applet_stopped = Se le dijo al applet de Getdown que dejara de trabajar.
+
+# application/digest errors
+m.missing_appbase = Al archivo de configuraci\u00f3n le falta el 'appbase'.
+m.invalid_version = El archivo de configuraci\u00f3n especifica una versi\u00f3n no v\u00e1lida.
+m.invalid_appbase = El archivo de configuraci\u00f3n especifica un 'appbase' no v\u00e1lido.
+m.missing_class = Al archivo de configuraci\u00f3n le falta la clase de aplicaci\u00f3n.
+m.missing_code = El archivo de configuraci\u00f3n especifica que no hay recursos de c\u00f3digo.
+m.invalid_digest_file = El archivo digest no es v\u00e1lido.
+
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties
new file mode 100644 (file)
index 0000000..3666204
--- /dev/null
@@ -0,0 +1,111 @@
+#
+# $Id: messages.properties 485 2012-03-08 22:05:30Z ray.j.greenwell $
+#
+# Getdown translation messages
+
+m.abort_title = Annuler l'installation?
+m.abort_confirm =<html>\u00cates-vous s\u00fbr de vouloir annuler l'installation? \
+   Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau.</html>
+m.abort_ok = Quitter
+m.abort_cancel = Continuer l'installation
+
+m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy
+
+m.configure_proxy =<html>Connexion au serveur impossible. \
+   <ul><li>  Veuillez v\u00e9rifier que <code>javaw.exe</code> n'est bloqu\u00e9 \
+   par aucun pare-feu ou antivirus. \
+   Vous pouvez vous rendre sur la configuration du pare-feu windows via \
+   (D\u00e9marrer ->  Panneau de Configuration ->  Pare-feu Windows ).</ul> \
+   <p>  Il est \u00e9galement possible que vous soyez derri\u00e8re un proxy que l'application \
+   est incapable de d\u00e9tecter automatiquement. \
+   Si tel est le cas, veuillez saisir les r\u00e9glages proxy ci-dessous.</html>
+
+m.proxy_extra =<html>Si vous \u00eates certain de ne pas utiliser de proxy, il est \
+   possible qu'une interruption temporaire de la connexion internet emp\u00fbche la \
+   communication avec les serveurs. Dans ce cas, vous pouvez relancer \
+   l'installation ult\u00e9rieurement.</html>
+
+m.proxy_host = Proxy IP
+m.proxy_port = Proxy port
+m.proxy_username = Nom d'utilisateur
+m.proxy_password = Mot de passe
+m.proxy_auth_required = Identification requise
+m.proxy_ok = OK
+m.proxy_cancel = Annuler
+
+m.downloading_java = T\u00e9l\u00e9chargement en cours de la Machine Virtuelle Java
+m.unpacking_java = D\u00e9compression en cours de la Machine Virtuelle Java
+
+m.resolving = R\u00e9solution des t\u00e9l\u00e9chargements en cours
+m.downloading = T\u00e9l\u00e9chargement des donn\u00e9es en cours
+m.failure = \u00c9chec du t\u00e9l\u00e9chargement: {0}
+
+m.checking = V\u00e9rification de la mise-\u00e0-jour en cours
+m.validating = Validation en cours
+m.patching = Modification en cours
+m.launching = Lancement en cours
+
+m.patch_notes = Notes de mise-\u00e0-jour
+
+m.complete = Complet \u00e0 {0}%
+m.remain = {0} restant
+
+m.updating_metadata = T\u00e9l\u00e9chargement des fichiers de contr\u00f4les en cours
+
+m.init_failed = Notre fichier de configuration est perdu ou corrompu. T\u00e9l\u00e9chargement \
+   d'une nouvelle copie en cours ...
+
+m.java_download_failed = Impossible de t\u00e9l\u00e9charger automatiquement la \
+   version de Java n\u00e9cessaire.\n\n\
+   Veuillez vous rendre sur www.java.com et t\u00e9l\u00e9charger et installer la version \
+   la plus r\u00e9cente de Java, avant d'ex\u00e9cuter l'application \u00e0 nouveau.
+
+m.java_unpack_failed = Impossible de d\u00e9compresser la version de \
+   Java n\u00e9cessaire. Veuillez v\u00e9rifier que vous avez au moins 100 MB d'espace libre \
+   sur votre disque dur puis tenter d'ex\u00e9cuter l'application \u00e0 nouveau.\n\n\
+   Si le probl\u00e8me persiste, rendez vous www.java.com et t\u00e9l\u00e9chargez et \
+   installez la version plus r\u00e9cente de Java puis essayez de nouveau.
+
+m.unable_to_repair = Impossible de t\u00e9l\u00e9charger les fichiers n\u00e9cessaires apr\u00e8s \
+   cinq tentatives. Vous pouvez tenter d'ex\u00e9cuter l'application \u00e0 nouveau, mais il est \
+   possible qu'une d\u00e9sinstallation / r\u00e9installation soit n\u00e9cessaire.
+
+m.unknown_error = Une erreur inconnue a fait \u00e9chouer le lancement de l'application. \
+   Veuillez visiter\n{0} pour plus d'informations.
+m.init_error = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause de l'erreur \
+   suivante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations.
+
+m.readonly_error = Le r\u00e9pertoire d'installation de cette application: \
+   \n{0}\nest en lecture seule. Veuillez installer l'application dans un r\u00e9pertoire avec \
+   un acc\u00e8s en \u00e9criture.
+
+m.missing_resource = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause d'une \
+   ressource manquante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations.
+
+m.insufficient_permissions_error = Vous n'avez pas accepter la signature \
+  num\u00e9rique de cette application. Si vous souhaitez ex\u00e9cuter cette application, vous \
+  devez accepter sa signature num\u00e9rique.\n\nAfin de le faire, vous devez quitter votre \
+  navigateur, le red\u00e9marrer, retourner \u00e0 cette page puis relancer l'application. \
+  Une fois la bo\u00eete de dialogue de s\u00e9curit\u00e9 affich\u00e9e, cliquez sur le bouton \
+  pour accepter la signature num\u00e9rique et accorder les permissions n\u00e9cessaires au bon \
+  fonctionnement de l'application.
+
+m.corrupt_digest_signature_error = Nous ne pouvons pas v\u00e9rifier la signature num\u00e9rique \
+  de l'application.\nVeuillez v\u00e9rifier que vous lancez l'application \ndepuis \
+  la bonne adresse internet.
+
+m.default_install_error = la section de support du site
+
+m.another_getdown_running = Plusieurs instances d'installation de cette \
+  application sont d\u00e9j\u00e0 en cours d'ex\u00e9cution.  Cette instance va s'arr\u00eater \
+  afin de permettre aux autres d'aboutir.
+
+m.applet_stopped = L'appelet Getdown a \u00e9t\u00e9 stopp\u00e9e.
+
+# application/digest errors
+m.missing_appbase = Le fichier de configuration ne contient pas 'appbase'.
+m.invalid_version = Le fichier de configuration sp\u00e9cifie une version invalide.
+m.invalid_appbase = Le fichier de configuration sp\u00e9cifie un 'appbase' invalide.
+m.missing_class = Le fichier de configuration ne contient pas la classe de l'application.
+m.missing_code = Le fichier de configuration ne sp\u00e9cifie aucune ressource de code.
+m.invalid_digest_file = Le fichier digest est invalide.
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties
new file mode 100644 (file)
index 0000000..33b3260
--- /dev/null
@@ -0,0 +1,114 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Annullare l'installazione?
+m.abort_confirm = <html>Sei sicuro di voler annullare l'installazione? \
+  Potrai riprenderla in seguito, riavviando nuovamente l'applicazione.</html>
+m.abort_ok = Chiudi
+m.abort_cancel = Continua l'installazione
+
+m.detecting_proxy = Provo a recuperare le configurazioni del proxy
+
+m.configure_proxy = <html>Impossibile collegarsi al server per \
+  recuperare i dati. \
+  <ul><li> Se il Firewall di Windows o Norton Internet Security bloccano \
+  <code>javaw.exe</code> non si possono scaricare i dati. Devi \
+  permettere a <code>javaw.exe</code> di accedere a internet. Puoi provare \
+  di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \
+  del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).</ul> \
+  <p> Il tuo computer potrebbe accedere a internet attraverso un proxy e \
+  questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \
+  tue impostazioni del proxy, puoi inserirle di seguito.</html>
+
+m.proxy_extra = <html>Se sei sicuro di non usare proxy  \
+  potrebbe essere un problema di internet o di collegamento con il server. \
+  In questo caso puoi annullare e ripetere l'installazione più tardi.</html>
+
+m.proxy_host = IP Proxy
+m.proxy_port = Porta Proxy
+m.proxy_username = Nome utente
+m.proxy_password = Parola d'ordine
+m.proxy_auth_required = Autenticazione richiesta
+m.proxy_ok = OK
+m.proxy_cancel = Annulla
+
+m.downloading_java = Scaricando la Java Virtual Machine
+m.unpacking_java = Scompattando la Java Virtual Machine
+
+m.resolving = Recuperando i file da scaricare
+m.downloading = Download dei dati
+m.failure = Download fallito: {0}
+
+m.checking = Sto controllando gli aggiornamenti
+m.validating = Validazione
+m.patching = Applico le patch
+m.launching = Avvio
+
+m.patch_notes = Note delle Patch
+m.play_again = Avvia Nuovamente
+
+m.complete = {0}% completato
+m.remain = {0} rimasto
+
+m.updating_metadata = Scarico i file di controllo
+
+m.init_failed = La configurazione è corrotta o mancante. Provo a \
+  scaricarne una nuova copia...
+
+m.java_download_failed = Impossibile scaricare la versione corretta \
+  di Java per il tuo computer.\n\n\
+  Visita www.java.com e scarica l'ultima versione di \
+  Java, poi lancia di nuovo l'applicazione.
+
+m.java_unpack_failed = Impossibile scompattare l'aggiornamento di \
+  Java. Verifica di avere almeno 100 MB di spazio libero nel tuo \
+  hard disk e prova a rilanciare l'applicazione.\n\n\
+  Se l'errore persiste, vistia www.java.com, scarica e \
+  installa l'ultima versione di Java e riprova.
+
+m.unable_to_repair = Impossibile scaricare i file necessari dopo 5 \
+  tentativi. Puoi provare a rilanciare l'applicazione, ma se fallisce \
+  di nuovo potresti dover reinstallarla.
+
+m.unknown_error = L'applicazione non è stata avviata a causa di uno strano \
+  errore che non conosco. Visita\n{0} per avere informazioni \
+  in merito.
+m.init_error = L'applicazione non è stata avviata a causa del seguente \
+  errore:\n{0}\n\nVistita\n{1} per avere \
+  informazioni su come risolvere il problema.
+
+m.readonly_error = La directory dove l'applicazione è installata: \
+  \n{0}\nè in sola lettura. Installa l'applicazione dove hai i diritti \
+  di scrittura.
+
+m.missing_resource = L'applicazione non è stata avviata a causa di mancanza \
+  di risorse:\n{0}\n\nVisita\n{1} per avere informazioni su come risolvere \
+  questi problemi.
+
+m.insufficient_permissions_error = Non hai accettato la \
+ firma digitale. Se vuoi eseguire l'applicazione devi accettare la \
+ firma digitale.\n\nPer farlo, riavvia il tuo browser \
+ e ritorna in questa pagina per rilanciare l'applicazione. Quando l'avviso \
+ di sicurezza viene mostrato, clicca per accettare la firma digitale \
+ ed eseguire l'applicazione con i privilegi necessari.
+
+m.corrupt_digest_signature_error = Impossibile verificare la firma digitale dell'applicazione \
+ .\nControlla di aver lanciato l'applicazione dal\n\
+ sito web corretto.
+
+m.default_install_error = la sezione di supporto del sito
+
+m.another_getdown_running = E' già in esecuzione un'istanza del programma. \
+ Questa verrà chiusa.
+
+m.applet_stopped = L'applet di Getdown è stata interrotta.
+
+# application/digest errors
+m.missing_appbase = Il tag "appbase" è mancante.
+m.invalid_version = Il file di configurazione non contiene una versione valida (tag "version").
+m.invalid_appbase = Il tag "appbase" non è valido.
+m.missing_class = Il file di configurazione non contiene la classe da eseguire (tag "class").
+m.missing_code = Il file di configurazione non contiene alcuna risorsa (tag "code").
+m.invalid_digest_file = Il file di digest non è valido.
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties
new file mode 100644 (file)
index 0000000..c344c16
--- /dev/null
@@ -0,0 +1,107 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f 
+m.abort_confirm = <html>\u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f  \
+  \u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002</html> 
+m.abort_ok = \u4e2d\u6b62 
+m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c 
+
+m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d
+
+m.configure_proxy = <html>\u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \
+  \u5931\u6557\u3057\u307e\u3057\u305f\u3002  \
+  <ul><li>\u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \
+  <code>javaw.exe</code>\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002  \u8a2d\u5b9a\u3092 \
+  <code>javaw.exe</code>\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002  \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \
+  \u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \
+  \u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002</ul> \
+  <p>\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \
+  \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002  \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \
+  \u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002</html> 
+
+m.proxy_extra = <html>\u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \
+  \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \
+  \u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002  \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \
+  \u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002</html> 
+
+m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP 
+m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8
+m.proxy_username = Username
+m.proxy_password = Password
+m.proxy_auth_required = Authentication required
+m.proxy_ok = OK  
+m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb 
+
+m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d
+m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d
+
+m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d
+m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d
+m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a  {0} 
+
+m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d
+m.validating = \u8a8d\u8a3c\u4e2d
+m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d
+m.launching = \u5b9f\u884c\u4e2d
+
+m.complete = {0}\uff05\u5b8c\u4e86 
+m.remain = \u3000\u6b8b\u308a{0} 
+
+m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d
+
+m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002  \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \
+  \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026 
+
+m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \
+  \u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \
+  www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \
+  \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \
+  \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002  \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \
+  \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \
+  \u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \
+  \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 
+
+m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \
+  \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002  \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \
+  \u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \
+  \u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002  \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \
+  \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \
+  \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \
+  \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f  \
+  \n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002  \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \
+  \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \
+  \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \
+  \u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \
+ \u3055\u308c\u307e\u3057\u305f\u3002  \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \
+ \u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \
+ \u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044  \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \
+ \u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \
+ \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \
+ \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \
+ \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 
+
+m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a 
+
+# application/digest errors
+m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002 
+m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 
+m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 
+m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002 
+m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 
+m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002 
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties
new file mode 100644 (file)
index 0000000..3f8a47f
--- /dev/null
@@ -0,0 +1,102 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
+m.abort_confirm = <html>\uC815\uB9D0\uB85C \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \
+  \uB098\uC911\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uC5EC \uC124\uCE58\uB97C \uC7AC\uAC1C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624.</html>
+m.abort_ok = \uC911\uC9C0
+m.abort_cancel = \uACC4\uC18D\uD558\uC5EC \uC124\uCE58
+
+m.detecting_proxy = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4
+
+m.configure_proxy = <html>\uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\
+  <ul><li>\uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 <code>javaw.exe</code>\uC774 \uC124\uC815\uC5D0\uC11C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC744 \uACBD\uC6B0, \
+  \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \
+  <code>javaw.exe</code>\uAC00 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD560 \uC218 \uC788\uB3C4\uB85D \uC124\uC815\uC744 \uBCC0\uACBD\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \
+  \uAC8C\uC784\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uD6C4, \uBC29\uD654\uBCBD \uC124\uC815\uC5D0\uC11C javaw.exe\uB97C \uC0AD\uC81C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \
+  ( \uC2DC\uC791 -> \uC81C\uC5B4\uD310 -> \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD )</ul> \
+  <p> \uCEF4\uD4E8\uD130\uAC00 \uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uD1B5\uD574 \uC778\uD130\uB137\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB2E4\uBA74, \uD504\uB85D\uC2DC \uC124\uC815\uC758 \uC790\uB3D9 \uAD6C\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \
+  \uC0AC\uC6A9\uD558\uB294 \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uC54C\uACE0 \uC788\uC744 \uACBD\uC6B0 \uC544\uB798\uC5D0 \uC785\uB825\uD558\uC5EC \uC8FC\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4.</html>
+
+m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4
+
+m.proxy_host = \uD504\uB85D\uC2DC IP
+m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8
+m.proxy_username = Username
+m.proxy_password = Password
+m.proxy_auth_required = Authentication required
+m.proxy_ok = OK
+m.proxy_cancel = \uCDE8\uC18C
+
+m.downloading_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uB2E4\uC6B4\uB85C\uB4DC \uC911
+m.unpacking_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uC555\uCD95\uC744 \uD574\uC81C\uD558\uB294 \uC911
+
+m.resolving = \uB2E4\uC6B4\uB85C\uB4DC \uBD84\uC11D \uC911
+m.downloading = \uB370\uC774\uD130 \uB2E4\uC6B4\uB85C\uB4DC \uC911
+m.failure = \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: {0}
+
+m.checking = \uC5C5\uB370\uC774\uD2B8 \uCCB4\uD06C
+m.validating = \uC720\uD6A8\uC131 \uAC80\uC0AC \uC911
+m.patching = \uD328\uCE58 \uC911
+m.launching = \uC2E4\uD589 \uC911
+
+m.patch_notes = \uD328\uCE58 \uB178\uD2B8
+m.play_again = \uB2E4\uC2DC \uC2E4\uD589
+
+m.complete = {0}% \uC644\uB8CC
+m.remain = {0} \uB0A8\uC74C
+
+m.updating_metadata = \uCEE8\uD2B8\uB864 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911
+
+m.init_failed = \uC124\uC815 \uD30C\uC77C\uC774 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uBCC0\uD615\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \
+  \uC0C8\uB85C\uC6B4 \uBCF5\uC0AC\uBCF8\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911\uC785\uB2C8\uB2E4...
+
+m.java_download_failed = \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uD544\uC694\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804\uC758 \uC790\uBC14\uB97C \uC790\uB3D9\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\n\
+  \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \
+  \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.
+
+m.java_unpack_failed = \uC5C5\uB370\uC774\uD2B8\uB41C \uBC84\uC804\uC758 \uC790\uBC14\uC758 \uC555\uCD95\uC744 \uD480 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \
+  \uD558\uB4DC\uB4DC\uB77C\uC774\uBE0C\uC5D0 \uCD5C\uC18C\uD55C 100MB\uC758 \uC6A9\uB7C9\uC744 \uD655\uBCF4\uD55C \uC774\uD6C4, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.\n\n\
+  \uB9CC\uC57D \uBB38\uC81C\uAC00 \uD574\uACB0\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \
+  \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.
+
+m.unable_to_repair = \uB2E4\uC12F\uBC88\uC758 \uC2DC\uB3C4\uC5D0\uB3C4 \uD544\uC694\uD55C \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \
+  \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD574\uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD55C\uB2E4\uBA74, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC81C\uAC70\uD55C \uD6C4, \uB2E4\uC2DC \uC2E4\uD589\uD574\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.unknown_error = \uBCF5\uAD6C\uB420 \uC218 \uC5C6\uB294 \uC624\uB958\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \
+  \n{0}\uC5D0 \uB300\uD55C \uBCF5\uAD6C \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.init_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC544\uB798\uC640 \uAC19\uC740 \uC5D0\uB7EC\uB85C \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC5D0\uB7EC:\
+  \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.readonly_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC124\uCE58\uB41C \uB514\uB809\uD1A0\uB9AC: \
+  \n{0}\n\uAC00 \uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC77D\uAE30 \uAD8C\uD55C\uC774 \uC2B9\uC778\uB41C \uB809\uD1A0\uB9AC\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC124\uCE58\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.missing_resource = \uB9AC\uC18C\uC2A4\uC758 \uC190\uC2E4\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. : \
+  \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.insufficient_permissions_error = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \
+ \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uAE30 \uC704\uD574\uC11C \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \
+ \n\n\uADF8\uB9AC\uACE0 \uB098\uC11C \uC6F9 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uACE0 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC5EC \uC6F9\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC640 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC7AC\uC2DC\uC791\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. \
+ \uBCF4\uC548\uC5D0 \uB300\uD55C \uB300\uD654\uC0C1\uC790\uAC00 \uBCF4\uC774\uBA74, \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC5D0 \uB300\uD55C \uD655\uC778 \uBC84\uD2BC\uC744 \uD074\uB9AD\uD558\uACE0, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uAE30 \uC704\uD55C \
+ \uAD8C\uD55C\uC744 \uBD80\uC5EC\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.corrupt_digest_signature_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \
+ \uC62C\uBC14\uB978 \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uACE0 \uC788\uB294 \uC9C0 \uD655\uC778\uBC14\uB78D\uB2C8\uB2E4.
+
+m.default_install_error = \uC6F9\uC0AC\uC774\uD2B8\uC758 \uC9C0\uC6D0 \uBA54\uB274(support section)\uB97C \uD655\uC778\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4.
+
+m.another_getdown_running = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uC778\uC2A4\uD1A8\uB7EC\uC758 \uB2E4\uC911 \uC778\uC2A4\uD134\uC2A4\uAC00 \uC2E4\uD589\uC911\uC785\uB2C8\uB2E4. \
+ \uD558\uB098\uAC00 \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uC911\uB2E8\uB429\uB2C8\uB2E4.
+
+m.applet_stopped = Getdown \uC560\uD50C\uB9BF \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
+
+# application/digest errors
+m.missing_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 'appbase' \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
+m.invalid_version = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C \uBC84\uC804\uC774 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.
+m.invalid_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C 'appbase'\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.
+m.missing_class = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uD074\uB798\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
+m.missing_code = \uC124\uC815 \uD30C\uC77C\uC5D0 \uB9AC\uC18C\uC2A4\uC5D0 \uB300\uD55C \uCF54\uB4DC\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
+m.invalid_digest_file = \uB2E4\uC774\uC81C\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties
new file mode 100644 (file)
index 0000000..47db91c
--- /dev/null
@@ -0,0 +1,118 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Cancelar a instala\u00E7\u00E3o?
+m.abort_confirm = <html>Tem certeza que deseja cancelar a instala\u00E7\u00E3o? \
+  Voc\u00EA pode continuar a instala\u00E7\u00E3o mais tarde, \
+  basta executar a aplica\u00E7\u00E3o novamente.</html>
+m.abort_ok = Sair
+m.abort_cancel = Continuar a instala\u00E7\u00E3o
+
+m.detecting_proxy = Tentando detectar automaticamente as configura\u00E7\u00F5es de proxy
+
+m.configure_proxy = <html>N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \
+  fazer o download dos dados. \
+  <ul><li> Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \
+  para bloquear o programa <code>javaw.exe</code> n\u00E3o ser\u00E1 poss\u00EDvel realizar \
+  o download. Voc\u00EA ter\u00E1 que permitir que o programa <code>javaw.exe</code> acesse \
+  a internet. Voc\u00EA pode tentar executar o programa novamente, mas voc\u00EA precisa \
+  remover o programa javaw.exe das configura\u00E7\u00F5es do firewall (Iniciar -> Painel \
+  de controle -> Firewall do Windows).</ul> \
+  <p> Seu computador pode estar acessando a internet atrav\u00E9s de um proxy e n\u00E3o foi \
+  capaz de detectar automaticamente as configura\u00E7\u00F5es de proxy. \
+  Voc\u00EA pode informar esses dados abaixo.</html>
+
+m.proxy_extra = <html>Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u00E3o pode ser \
+  que exista um problema tempor\u00E1rio que est\u00E1 impedindo a comunica\u00E7\u00E3o \
+  com os nossos servidores. Neste caso voc\u00EA pode cancelar e tentar instalar novamente \
+  mais tarde.</html>
+
+m.proxy_host = IP do Proxy
+m.proxy_port = Porta do Proxy
+m.proxy_username = Nome de usu\u00e1rio
+m.proxy_password = Senha
+m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida
+m.proxy_ok = OK
+m.proxy_cancel = Cancelar
+
+m.downloading_java = Fazendo o download da m\u00E1quina virtual Java
+m.unpacking_java = Descompactando a m\u00E1quina virtual Java
+
+m.resolving = Resolvendo downloads
+m.downloading = Transferindo dados
+m.failure = Download falhou: {0}
+
+m.checking = Verificando atualiza\u00E7\u00F5es
+m.validating = Validando
+m.patching = Atualizando
+m.launching = Executando
+
+m.patch_notes = Corrigir notas
+m.play_again = Jogar de novo
+
+m.complete = {0}% completo
+m.remain = {0} Permanecer
+
+m.updating_metadata = Transferindo arquivos de controle
+
+m.init_failed = Nosso arquivo de configura\u00E7\u00E3o est\u00E1 ausente ou corrompido. Tente \
+  baixar uma nova c\u00F3pia...
+
+m.java_download_failed = N\u00E3o conseguimos baixar automaticamente a\
+  vers\u00E3o necess\u00E1ria do Java para o seu computador.\n\n\
+  Por favor, acesse www.java.com, baixe e instale a \u00FAltima vers\u00E3o do \
+  Java, em seguida, tente executar o aplicativo novamente.
+
+m.java_unpack_failed = N\u00E3o conseguimos descompactar uma vers\u00E3o atualizada do \
+  Java. Por favor, certifique-se de ter pelo menos 100 MB de espa\u00E7o livre em seu \
+  disco r\u00EDgido e tente executar o aplicativo novamente. \n\n\
+  Se isso n\u00E3o resolver o problema, acesse www.java.com,baixe e \
+  instale a \u00FAltima vers\u00E3o do Java e tente novamente.
+
+m.unable_to_repair = N\u00E3o conseguimos baixar os arquivos necess\u00E1rios depois de \
+  cinco tentativas. Voc\u00EA pode tentar executar o aplicativo novamente, mas se ele \
+  falhar pode ser necess\u00E1rio desinstalar e reinstalar.
+
+m.unknown_error = A aplica\u00E7\u00E3o falhou ao iniciar devido a algum erro estranho \
+  do qual n\u00E3o conseguimos recuperar. Por favor, visite \n{0} para obter \
+  informa\u00E7\u00F5es sobre como recuperar.
+m.init_error = A aplica\u00E7\u00E3o falhou ao iniciar devido ao seguinte \
+  erro:\n{0}\n\nPor favor visite \n{1} para \
+  informa\u00E7\u00F5es sobre como lidar com esse problema.
+
+m.readonly_error =O diret\u00F3rio no qual este aplicativo est\u00E1 instalado: \
+  \n{0}\n \u00E9 somente leitura. Por favor, instale o aplicativo em um diret\u00F3rio onde \
+  voc\u00EA tem acesso de grava\u00E7\u00E3o.
+
+m.missing_resource = A aplica\u00E7\u00E3o falhou ao iniciar devido a uma falta \
+  de recurso:\n{0}\n\n Por favor, visite\n{1} para obter informa\u00E7\u00F5es sobre \
+  como lidar com tal problema.
+
+m.insufficient_permissions_error = Voc\u00EA n\u00E3o aceitou a assinatura digital \
+  do aplicativo. Se voc\u00EA quiser executar o aplicativo, voc\u00EA ter\u00E1 que aceitar \
+  a assinatura digital. \n\nPara fazer isso, voc\u00EA ter\u00E1 que sair do seu navegador, \
+  reinici\u00E1-lo, e retornar a esta p\u00E1gina web para executar a aplica\u00E7\u00E3o. \
+  Quando o di\u00E1logo de seguran\u00E7a aparecer, clique no bot\u00E3o para aceitar a \
+  assinatura digital e conceder a este aplicativo os privil\u00E9gios necess\u00E1rios \
+  para executar.
+
+m.corrupt_digest_signature_error = N\u00E3o conseguimos verificar a assinatura digital \
+  do aplicativo.\nPor favor, verifique se voc\u00EA est\u00E1 utilizando o aplicativo \nde um \
+  site correto.
+
+m.default_install_error = a se\u00E7\u00E3o de suporte do site
+
+m.another_getdown_running = V\u00E1rias inst\u00E2ncias desta aplica\u00E7\u00E3o \
+  est\u00E3o em execu\u00E7\u00E3o. Esta ir\u00E1 parar e deixar outra completar suas atividades.
+
+m.applet_stopped = Foi solicitado ao miniaplicativo GetDow que parasse de trabalhar.
+
+# application/digest errors
+m.missing_appbase = O arquivo de configura\u00E7\u00E3o n\u00E3o possui o 'AppBase'.
+m.invalid_version = O arquivo de configura\u00E7\u00E3o especifica uma vers\u00E3o inv\u00E1lida.
+m.invalid_appbase = O arquivo de configura\u00E7\u00E3o especifica um 'AppBase' inv\u00E1lido.
+m.missing_class = O arquivo de configura\u00E7\u00E3o n\u00E3o possui a classe de aplicativo.
+m.missing_code = O arquivo de configura\u00E7\u00E3o n\u00E3o especifica um recurso de c\u00F3digo.
+m.invalid_digest_file = O arquivo digest \u00E9 inv\u00E1lido.
diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties
new file mode 100644 (file)
index 0000000..2c27543
--- /dev/null
@@ -0,0 +1,61 @@
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668
+
+m.configure_proxy = <html>\u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \
+  \u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \
+  \u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002</html>
+
+m.proxy_extra = <html>\u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \
+  \u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002<br><br> \
+  \u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \
+  \u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002</html>
+
+m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740
+m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7
+m.proxy_username = Username
+m.proxy_password = Password
+m.proxy_auth_required = Authentication required
+m.proxy_ok = \u786e\u5b9a
+m.proxy_cancel = \u53d6\u6d88
+
+m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9
+m.downloading = \u4e0b\u8f7d\u6570\u636e
+m.failure = \u4e0b\u8f7d\u5931\u8d25: {0}
+
+m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9
+m.validating = \u786e\u8ba4
+m.patching = \u5347\u7ea7
+m.launching = \u542f\u52a8
+
+m.complete = {0}% \u5b8c\u6210
+m.remain = {0} \u5269\u4f59\u65f6\u95f4
+
+m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6
+
+m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d...
+
+m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\
+\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002
+
+
+m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\
+\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002
+
+m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \
+\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002
+
+
+m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \
+\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002
+
+# application/digest errors
+m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002
+m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002
+m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002
+m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002
+m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002
+m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002
diff --git a/getdown/src/getdown/lib/SOURCE_HEADER b/getdown/src/getdown/lib/SOURCE_HEADER
new file mode 100644 (file)
index 0000000..43271fe
--- /dev/null
@@ -0,0 +1,5 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
diff --git a/getdown/src/getdown/lib/commons-compress-1.18.jar b/getdown/src/getdown/lib/commons-compress-1.18.jar
new file mode 100644 (file)
index 0000000..e401046
Binary files /dev/null and b/getdown/src/getdown/lib/commons-compress-1.18.jar differ
diff --git a/getdown/src/getdown/lib/jRegistryKey.dll b/getdown/src/getdown/lib/jRegistryKey.dll
new file mode 100644 (file)
index 0000000..5746728
Binary files /dev/null and b/getdown/src/getdown/lib/jRegistryKey.dll differ
diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar
new file mode 100644 (file)
index 0000000..5100795
Binary files /dev/null and b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar differ
diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom
new file mode 100644 (file)
index 0000000..226a7d7
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>jregistrykey</groupId>
+  <artifactId>jregistrykey</artifactId>
+  <version>1.0</version>
+  <description>POM was created from install:install-file</description>
+</project>
diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml b/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml
new file mode 100644 (file)
index 0000000..1a8a725
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+  <groupId>jregistrykey</groupId>
+  <artifactId>jregistrykey</artifactId>
+  <version>1.0</version>
+  <versioning>
+    <versions>
+      <version>1.0</version>
+    </versions>
+    <lastUpdated>20101118155146</lastUpdated>
+  </versioning>
+</metadata>
diff --git a/getdown/src/getdown/lib/manifest.mf b/getdown/src/getdown/lib/manifest.mf
new file mode 100644 (file)
index 0000000..3be50cc
--- /dev/null
@@ -0,0 +1,6 @@
+Main-Class: com.threerings.getdown.launcher.Getdown
+Permissions: all-permissions
+Application-Name: Getdown
+Codebase: *
+Application-Library-Allowable-Codebase: *
+Caller-Allowable-Codebase: *
diff --git a/getdown/src/getdown/mvn_cmd b/getdown/src/getdown/mvn_cmd
new file mode 100644 (file)
index 0000000..d984902
--- /dev/null
@@ -0,0 +1 @@
+mvn clean package -Dgetdown.host.whitelist=jalview.org,*.jalview.org && cp launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar ../../../getdown/lib/getdown-launcher.jar && cp core/target/getdown-core-1.8.3-SNAPSHOT.jar ../../../getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar && cp core/target/getdown-core-1.8.3-SNAPSHOT.jar ../../../j8lib/getdown-core.jar && cp core/target/getdown-core-1.8.3-SNAPSHOT.jar ../../../j11lib/getdown-core.jar 
diff --git a/getdown/src/getdown/pom.xml b/getdown/src/getdown/pom.xml
new file mode 100644 (file)
index 0000000..17cb8f6
--- /dev/null
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+
+  <groupId>com.threerings.getdown</groupId>
+  <artifactId>getdown</artifactId>
+  <packaging>pom</packaging>
+  <version>1.8.3-SNAPSHOT</version>
+
+  <name>getdown</name>
+  <description>An application installer and updater.</description>
+  <url>https://github.com/threerings/getdown</url>
+  <issueManagement>
+    <url>https://github.com/threerings/getdown/issues</url>
+  </issueManagement>
+
+  <licenses>
+    <license>
+      <name>The (New) BSD License</name>
+      <url>http://www.opensource.org/licenses/bsd-license.php</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <developers>
+    <developer>
+      <id>samskivert</id>
+      <name>Michael Bayne</name>
+      <email>mdb@samskivert.com</email>
+    </developer>
+  </developers>
+
+  <scm>
+    <connection>scm:git:git://github.com/threerings/getdown.git</connection>
+    <developerConnection>scm:git:git@github.com:threerings/getdown.git</developerConnection>
+    <url>https://github.com/threerings/getdown</url>
+  </scm>
+
+  <modules>
+    <module>core</module>
+    <module>launcher</module>
+    <module>ant</module>
+  </modules>
+
+  <repositories>
+       <repository>
+          <id>ej-technologies</id>
+          <url>https://maven.ej-technologies.com/repository</url>
+       </repository>
+  </repositories>
+
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-compress</artifactId>
+      <version>1.18</version>
+    </dependency>
+    <dependency>
+               <groupId>com.install4j</groupId>
+               <artifactId>install4j-runtime</artifactId>
+               <version>7.0.11</version>
+               <!--<scope>provided</scope>-->
+       </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.sonatype.plugins</groupId>
+        <artifactId>nexus-staging-maven-plugin</artifactId>
+        <version>1.6.8</version>
+        <extensions>true</extensions>
+        <inherited>false</inherited>
+        <configuration>
+          <serverId>ossrh-releases</serverId>
+          <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+          <stagingProfileId>aa555c46fc37d0</stagingProfileId>
+        </configuration>
+      </plugin>
+    </plugins>
+
+    <!-- Common plugin configuration for all children -->
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.7.0</version>
+          <configuration>
+            <source>1.7</source>
+            <target>1.7</target>
+            <fork>true</fork>
+            <showDeprecation>true</showDeprecation>
+            <showWarnings>true</showWarnings>
+            <compilerArgs>
+              <arg>-Xlint</arg>
+              <arg>-Xlint:-serial</arg>
+              <arg>-Xlint:-path</arg>
+            </compilerArgs>
+          </configuration>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>3.0.2</version>
+          <configuration>
+            <encoding>UTF-8</encoding>
+          </configuration>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <version>3.0.0-M1</version>
+          <configuration>
+            <quiet>true</quiet>
+            <show>public</show>
+            <additionalparam>-Xdoclint:all -Xdoclint:-missing</additionalparam>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>eclipse</id>
+      <activation>
+        <property>
+          <name>m2e.version</name>
+        </property>
+      </activation>
+      <build>
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <!-- Tell m2eclipse to ignore the enforcer plugin from our parent. Otherwise it warns
+                   about not being able to run it. -->
+              <groupId>org.eclipse.m2e</groupId>
+              <artifactId>lifecycle-mapping</artifactId>
+              <version>1.0.0</version>
+              <configuration>
+                <lifecycleMappingMetadata>
+                  <pluginExecutions>
+                    <pluginExecution>
+                      <pluginExecutionFilter>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-enforcer-plugin</artifactId>
+                        <versionRange>[1.0,)</versionRange>
+                        <goals>
+                          <goal>enforce</goal>
+                        </goals>
+                      </pluginExecutionFilter>
+                      <action>
+                        <ignore />
+                      </action>
+                    </pluginExecution>
+                  </pluginExecutions>
+                </lifecycleMappingMetadata>
+              </configuration>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+      </build>
+    </profile>
+
+    <profile>
+      <id>release-sign-artifacts</id>
+      <activation>
+        <property><name>performRelease</name><value>true</value></property>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-gpg-plugin</artifactId>
+            <version>1.6</version>
+            <executions>
+              <execution>
+                <id>sign-artifacts</id>
+                <phase>verify</phase>
+                <goals>
+                  <goal>sign</goal>
+                </goals>
+              </execution>
+            </executions>
+            <configuration>
+              <keyname>mdb@samskivert.com</keyname>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/gradle.properties b/gradle.properties
new file mode 100644 (file)
index 0000000..e49c3ab
--- /dev/null
@@ -0,0 +1,115 @@
+jalviewDir = .
+
+JAVA_VERSION = 11
+JALVIEW_VERSION = DEVELOPMENT
+INSTALLATION = Source
+jalview_keystore = keys/.keystore
+jalview_keystore.pass = alignmentisfun
+jalview_key = jalview
+jalview_key.pass = alignmentisfun
+jalview_tsaurl =
+proxyPort = 80
+proxyHost = sqid
+jalview_keyalg = SHA1withRSA
+jalview_keydig = SHA1
+
+testngGroups = Functional
+
+javac_source = 11
+javac_target = 11
+
+j8libDir = j8lib
+j11libDir = j11lib
+resourceDir = resources
+helpParentDir = help
+helpDir = help
+docDir = doc
+sourceDir = src
+schemaDir = schemas
+classesDir = classes
+clover = false
+use_clover = false
+cloverClassesDir = clover-classes
+cloverSourcesInstrDir = sources-instr
+packageDir = dist
+outputJar = jalview.jar
+
+testSourceDir = test
+testOutputDir = tests
+utilsDir = utils
+
+buildPropertiesFile = .build_properties
+application_codebase = *.jalview.org
+mainClass = jalview.bin.Jalview
+shadowJarMainClass = jalview.bin.Launcher
+launcherClass = jalview.bin.Jalview
+
+gradlePluginsDir = gradle/plugins
+
+getdown_local = false
+getdown_website_dir = getdown/website
+getdown_resource_dir = resource
+getdown_app_dir = dist
+getdown_j11lib_dir = j11lib
+getdown_files_dir = getdown/files
+getdown_launcher = getdown/lib/getdown-launcher.jar
+getdown_core = getdown/lib/getdown-core-1.8.3-SNAPSHOT.jar
+getdown_base_txt = getdown/files/getdown.txt
+getdown_txt_title = Jalview
+getdown_channel_base = http://www.jalview.org/getdown/jalview
+getdown_channel_name = TEST
+getdown_txt_allow_offline = true
+getdown_txt_jvmmempc = 95
+getdown_txt_multi_jvmarg = -Dgetdownappdir="%APPDIR%"
+getdown_txt_strict_comments = true
+getdown_txt_title = Jalview
+getdown_txt_ui.name = Jalview
+getdown_txt_ui.background = FFFFFF
+getdown_txt_ui.background_image = resources/images/jalview_logo_background_getdown-640x480.png
+getdown_txt_ui.error_background = resources/images/jetset_jalview_splash.png
+getdown_txt_ui.progress_image = resources/images/jalview_logo_background_getdown-progress.png
+getdown_txt_ui.icon = resources/images/JalviewLogo_Huge.png
+getdown_txt_ui.progress = 20, 440, 600, 22
+getdown_txt_ui.progress_bar = AAAAFF
+getdown_txt_ui.progress_text = 000000
+getdown_txt_ui.status = 20, 380, 600, 58
+getdown_txt_ui.status_text = 000066
+getdown_txt_ui.text_shadow = EEEEFF
+getdown_txt_ui.install_error = http://www.jalview.org/download/error
+getdown_txt_ui.mac_dock_icon = resources/images/jalview_logos.ico
+getdown_alt_java8_min_version  = 01080000
+getdown_alt_java11_min_version = 11000000
+getdown_alt_java11_txt_multi_java_location = [windows-amd64] /getdown/jre/windows-jre11.tgz,[linux-amd64] /getdown/jre/linux-jre11.tgz,[mac os x] /getdown/jre/macos-jre11.tgz
+getdown_alt_java8_txt_multi_java_location = [windows-amd64] /getdown/jre/windows-jre1.8.tgz,[linux-amd64] /getdown/jre/linux-jre1.8.tgz,[mac os x] /getdown/jre/macos-jre1.8.tgz
+JRE_installs = /Users/bsoares/Java/installs
+Windows_JRE8 = OpenJDK8U-jdk_x64_windows_hotspot_8u202b08/jdk8u202-b08
+Mac_JRE8 = OpenJDK8U-jre_x64_mac_hotspot_8u202b08/jdk8u202-b08-jre/Contents/Home
+Linux_JRE8 = OpenJDK8U-jre_x64_linux_hotspot_8u202b08/jdk8u202-b08-jre
+Windows_JRE11 = OpenJDK11-jre_x64_windows_hotspot_11_28/jdk-11+28-jre
+Mac_JRE11 = OpenJDK11-jre_x64_mac_hotspot_11_28/jdk-11+28-jre/Contents/Home
+Linux_JRE11 = OpenJDK11-jre_x64_linux_hotspot_11_28/jdk-11+28-jre
+
+j8libDir = j8lib
+j11libDir = j11lib
+j11modDir = j11mod
+j11modules = com.sun.istack.runtime,com.sun.xml.bind,com.sun.xml.fastinfoset,com.sun.xml.streambuffer,com.sun.xml.txw2,com.sun.xml.ws.policy,java.activation,java.annotation,java.base,java.compiler,java.datatransfer,java.desktop,java.logging,java.management,java.management.rmi,java.naming,java.prefs,java.rmi,java.scripting,java.security.sasl,java.sql,java.xml,java.xml.bind,java.xml.soap,java.xml.ws,javax.jws,jdk.httpserver,jdk.jsobject,jdk.unsupported,jdk.xml.dom,org.jvnet.mimepull,org.jvnet.staxex,javax.servlet.api,java.ws.rs
+
+install4jCopyrightMessage = ...
+install4jMacOSBundleId = org.jalview.jalview-desktop
+install4jResourceDir = utils/install4j
+install4jTemplate = install4j_template.install4j
+install4jInfoPlistFileAssociations = file_associations_auto-Info_plist.xml
+install4jInstallerFileAssociations = file_associations_auto-install4j.xml
+install4jBuildDir = build/install4j
+install4jMediaTypes = windows,macosArchive,linuxRPM,linuxDeb,unixArchive
+install4jMediaTypes = windows,macosArchive,linuxRPM,linuxDeb,unixArchive,unixInstaller
+install4jFaster = false
+
+OSX_KEYSTORE =
+OSX_KEYPASS =
+JSIGN_SH = echo
+
+eclipse_extra_jdt_prefs_file = .settings/org.eclipse.jdt.core.jalview.prefs
+
+dev = false
+
similarity index 100%
rename from help/help.hs
rename to help/help/help.hs
similarity index 100%
rename from help/help.jhm
rename to help/help/help.jhm
similarity index 100%
rename from help/helpTOC.xml
rename to help/help/helpTOC.xml
similarity index 100%
rename from help/html/align.jpg
rename to help/help/html/align.jpg
similarity index 96%
rename from help/html/calculations/pca.html
rename to help/help/html/calculations/pca.html
index 5b76d10..3529cae 100755 (executable)
     within the similarity space, as points in a rotateable 3D
     scatterplot. The colour of each sequence point is the same as the
     sequence group colours, white if no colour has been defined for the
-    sequence, and green if the sequence is part of a the currently
-    selected group.</p>
+    sequence, and grey if the sequence is part of the currently selected
+    group. The viewer also employs depth cueing, so points appear darker
+    the farther away they are, and become brighter as they are rotated
+    towards the front of the view.</p>
   <p>
     The 3d view can be rotated by dragging the mouse with the <strong>left
       mouse button</strong> pressed. The view can also be zoomed in and out with
similarity index 99%
rename from help/html/features/featuresettings.html
rename to help/help/html/features/featuresettings.html
index 3d7c944..a156a39 100755 (executable)
     available in the application) will re-order the feature render
     ordering based on the average length of each feature type.
   </p>
-  <p>
+  <p><a name="transparency"></a>
     The <strong><em>transparency slider setting</em></strong> controls
     the visibility of features rendered below other features. Reducing
     the transparency will mean that features at the top of the list can
similarity index 95%
rename from help/html/io/export.html
rename to help/help/html/io/export.html
index 8a554aa..04216da 100755 (executable)
@@ -82,6 +82,8 @@
     blurred. Right-click the image and choose &quot;Edit image.&quot; to
     convert it to word drawing objects which give a truer WYSIWIG
     representation.</li>
+  <li>EPS does not support colour transparency, so will not reproduce this
+    if used for <a href="../features/featuresettings.html#transparency">feature colouring</a>.</li>
   <li>Mac OSX users will find that eps files are automatically
     converted into PDF files.</li>
   </p>
similarity index 100%
rename from help/html/keys.html
rename to help/help/html/keys.html
similarity index 92%
rename from help/html/menus/alwcalculate.html
rename to help/help/html/menus/alwcalculate.html
index c7a1c87..d08f713 100755 (executable)
         parsed into sequence associated annotation which can then be
         used to sort the alignment via the Sort by&#8594;Score menu.
     </em> <br></li>
-    <li><strong>Translate as cDNA</strong> (not applet)<br> <em>This
+    <li><strong>Translate as cDNA</strong><br> <em>This
         option is visible for nucleotide alignments. Selecting this
         option shows the DNA's calculated protein product in a new <a
         href="../features/splitView.html">split frame</a> window. Note
         that the translation is not frame- or intron-aware; it simply
-        translates all codons in each sequence, using the standard <a
-        href="../misc/geneticCode.html">genetic code</a> (any incomplete
-        final codon is discarded). You can perform this action on the
-        whole alignment, or selected rows, columns, or regions.
+        translates all codons in each sequence. You can use the standard <a
+        href="../misc/geneticCode.html">genetic code</a>, or choose an 
+        alternative code table (any incomplete final codon is discarded). 
+        You can perform this action on the whole alignment, or selected 
+        rows, columns, or regions. Alternative code tables were added in
+        Jalview 2.11.
     </em> <br></li>
     <li><strong>Reverse, Reverse Complement</strong> (not applet)<br>
       <em>These options are visible for nucleotide alignments.
similarity index 94%
rename from help/html/menus/alwfile.html
rename to help/help/html/menus/alwfile.html
index b8445af..57ffa08 100755 (executable)
           features</a> or <a href="../features/annotationsFormat.html">alignment
           annotations</a>.
     </em></li>
+    <li><strong>Load VCF File<br>
+    </strong><em>Load VCF annotations from a plain text or tab-indexed file.
+    <br>This option is offered for nucleotide alignments, and requires at least one
+    sequence to have known genomic coordinates.
+    <br>Genomic coordinates are attached to entries retrieved from Ensembl.
+    <br>Support for VCF was added in Jalview 2.11.
+    </em></li>
     <li><strong>Close (Control W)</strong><br> <em>Close
         the alignment window. Make sure you have saved your alignment
         before you close - either as a Jalview project or by using the <strong>Save
similarity index 94%
rename from help/html/releases.html
rename to help/help/html/releases.html
index 0fba08a..287b218 100755 (executable)
@@ -51,30 +51,91 @@ li:before {
   </p>
   <table border="1">
     <tr>
-      <td width="60" nowrap>
-        <div align="center">
-          <em><strong>Release</strong></em>
-        </div>
-      </td>
-      <td>
-        <div align="center">
-          <em><strong>New Features</strong></em>
-        </div>
-      </td>
-      <td>
-        <div align="center">
-          <em><strong>Issues Resolved</strong></em>
-        </div>
-      </td>
+      <th nowrap><em>Release</em></th>
+      <th><em>New Features</em></th>
+      <th><em>Issues Resolved</em></th>
     </tr>
-    
-      <td width="60" nowrap>
-        <div align="center">
-          <strong><a name="Jalview.2.11.0">2.11.0</a><br />
-            <em>29/01/2019</em></strong>
-        </div>
+    <tr>
+      <td width="60" align="center" nowrap>
+          <strong><a name="Jalview.2.11">2.11</a><br />
+            <em>14/05/2019 (final due date !)</em></strong>
       </td>
-    <td><div align="left">
+      <td align="left" valign="top">
+        <ul>
+          <li>
+            <!-- JAL-1793 -->Annotate nucleotide alignments from VCF data files</li>
+          <li>
+            <!-- JAL-3141 -->Optional automatic backups when saving
+            Jalview project or alignment files</li>
+          <li>
+            <!-- JAL-1767,JAL-2647 -->Principal Components Analysis and
+            Viewer state is saved in Jalview Project<br />The 'Change
+            parameters' option has also been removed from the PCA viewer</li>
+          <li>
+            <!-- JAL-3127 -->New 'Colour by Sequence ID' (subgroup) option</li>
+          <li>
+            <!-- JAL-2620 -->Alternative genetic code tables supported for
+            'Translate as cDNA'</li>
+          <li>
+            <!-- JAL-2933 -->Finder panel remembers last position in each view</li>
+          <li>
+            <!-- JAL-3198 -->More efficient creation of selections and
+            multiple groups when working with large alignments</li>
+          <li>
+            <!-- JAL-3200 -->Speedier import of annotation rows when
+            parsing stockholm files</li>
+          <li>
+            <!-- JAL-3018 -->Update of Ensembl Rest Client to API v10.0</li>
+          <li>
+            <!-- JAL-2808,JAL-2069 -->Sequence features can be filtered and
+            shaded according to any associated attributes (e.g. variant
+            attributes from VCF file, or key-value pairs imported from
+            column 9 of GFF file)</li>
+          <li>
+            <!-- JAL-2897 -->Show synonymous codon variants on peptide sequences</li>
+          <li>
+            <!-- JAL-2792 -->Popup report of a selected sequence feature's details</li>
+          <li>
+            <!-- JAL-3139,JAL-2816 -->More efficient sequence feature render
+            algorithm (Z-sort/transparency and filter aware)</li>
+          <li>
+            <!-- JAL-2527 -->Alignment Overview now by default shows
+            only visible region of alignment (this can be changed in
+            user preferences)</li>
+          <li>
+            <!-- JAL-3169 -->File Chooser stays open after Cancel overwrite</li>
+          <li>
+            <!-- JAL-2420,JAL-3166 -->Better popup menu behaviour when
+          all sequences are hidden</li>
+          <li>
+            <!-- JAL-1244 -->Status bar shows bounds when dragging a selection
+          region, and gap count when inserting or deleting gaps</li>
+          <li>
+            <!-- JAL-3132 -->Status bar updates over sequence and annotation labels</li>
+          <li>
+            <!-- JAL-3093 -->Show annotation tooltips and popup menus in wrapped mode</li>
+          <li>
+            <!-- JAL-3073 -->Can select columns by dragging left/right in annotations</li>
+          <li>
+            <!-- JAL-3049,JAL-3054 -->Improved tooltips in Feature Settings dialog</li>
+          <li>
+            <!-- JAL-2814 -->Help button on Uniprot and PDB search panels</li>
+          <li> 
+            <!-- JAL-2621 -->Cursor changes over draggable box in Overview panel</li>
+          <li>
+            <!-- JAL-3203  -->Overview panel default changed to not show hidden regions</li>
+          <li>
+            <!-- JAL-3181 -->Consistent ordering of links in sequence id popup menu</li>
+          <li>
+            <!-- JAL-3021 -->Sequence Details report opens positioned to top of report</li>
+          <li>
+            <!-- JAL-3218 -->Scale panel popup menu allows Hide selected columns adjacent 
+            to a hidden column marker</li>
+          <li>
+            <!-- JAL-2975 -->Can use shift + arrow keys to rotate PCA image incrementally</li>
+          <li>
+            <!-- JAL-2965 -->PCA depth cueing with graduated colours</li>
+        </ul>
         <em>Deprecations</em>
         <ul>
           <li>
@@ -84,51 +145,131 @@ li:before {
         </ul>
         <em>Release Processes</em>
         <ul>
-        <li>Atlassian Bamboo continuous integration server for unattended Test Suite execution</li>
-        <li><!-- JAL-2864 -->Memory test suite to detect leaks in common operations</li> 
+          <li>
+          Atlassian Bamboo continuous integration server for
+            unattended Test Suite execution</li>
+          <li>
+            <!-- JAL-2864 -->Memory test suite to detect leaks in common
+            operations</li>
+          <li>
+            <!-- JAL-3140 -->IntervalStoreJ (new updatable NCList
+            implementation) used for Sequence Feature collections</li>
+          <li>
+            <!-- JAL-3063,JAL-3116 -->Castor library for XML marshalling and
+            unmarshalling has been replaced by JAXB for Jalview projects
+            and XML based data retrieval clients</li>
         </ul>
-      </div></td>
-    <td><div align="left">
-        <em></em>
+      </td>
+    <td align="left" valign="top">
         <ul>
           <li>
+            <!-- JAL-3143 -->Timeouts when retrieving data from Ensembl</li>
+          <li>
+            <!-- JAL-3244 -->'View [Structure] Mappings' and structure superposition in Jmol fail on Windows</li>
+          <li>
+            <!-- JAL-3239 -->Text misaligned in EPS or SVG image export with monospaced font</li>
+          <li>
+            <!-- JAL-3171 -->Warning of 'Duplicate entry' when saving
+            Jalview project involving multiple views</li>
+          <li>
+            <!-- JAL-3164 -->Overview for complementary view in a linked
+            CDS/Protein alignment is not updated when Hide Columns by
+            Annotation dialog hides columns</li>
+          <li>
+            <!-- JAL-3158 -->Selection highlighting in the complement of
+            a CDS/Protein alignment stops working after making a
+            selection in one view, then making another selection in the
+            other view</li>
+          <li>
+            <!-- JAL-3161 -->Annotations tooltip changes beyond visible columns</li>
+          <li>
+            <!-- JAL-3154 -->Table Columns could be re-ordered in
+            Feature Settings and Jalview Preferences panels</li>
+          <li>
             <!-- JAL-2865 -->Jalview hangs when closing windows
-            or the overview updates with large alignments.
-          </li>
+            or the overview updates with large alignments</li>
           <li>
-            <!-- JAL-2865 -->Tree and PCA calculation fails for selected
+            <!-- JAL-2750 -->Tree and PCA calculation fails for selected
             region if columns were selected by dragging right-to-left
-            and the mouse moved to the left of the first column.
-          </li>
+            and the mouse moved to the left of the first column</li>
           <li>
             <!-- JAL-2846 -->Error message for trying to load in invalid
-            URLs doesn't tell users the invalid URL
-          </li>
+            URLs doesn't tell users the invalid URL</li>
+          <li>
+            <!-- JAL-3178 -->Nonpositional features lose feature group
+            on export as Jalview features file</li>
+          <li>
+            <!-- JAL-3097,JAL-3099 -->Blank extra columns drawn or 
+            printed when columns are hidden</li>
+          <li>
+            <!-- JAL-3082 -->Regular expression error for '(' in Select Columns by Annotation description</li>
+          <li>
+            <!-- JAL-3072 -->Scroll doesn't stop on mouse up after 
+            dragging out of Scale or Annotation Panel</li>
+          <li>
+            <!-- JAL-3075 -->Column selection incorrect after scrolling out of scale panel</li>
+          <li>
+            <!-- JAL-3074 -->Left/right drag in annotation can scroll alignment down</li>
+          <li>
+            <!-- JAL-3108 -->Error if mouse moved before clicking Reveal in scale panel</li>
+          <li>
+            <!-- JAL-3002 -->Column display is out by one after Page Down, Page Up in wrapped mode</li>
+          <li>
+            <!-- JAL-2839 -->Finder doesn't skip hidden regions</li>
+          <li>
+            <!-- JAL-2932 -->Finder searches in minimised alignments</li>
+          <li>
+            <!-- JAL-2250 -->'Apply Colour to All Groups' not always selected on
+            opening an alignment</li>
+          <li>
+            <!-- JAL-3180 -->'Colour by Annotation' not marked selected in Colour menu</li>
+          <li>
+            <!-- JAL-3201 -->Per-group Clustal colour scheme changes when 
+            different groups in the alignment are selected</li>
+          <li>
+            <!-- JAL-2717 -->Internationalised colour scheme names not shown correctly in menu</li>
+          <li>
+            <!-- JAL-3206 -->Colour by Annotation can go black at min/max threshold limit</li>
+          <li>
+            <!-- JAL-3125 -->Value input for graduated feature colour threshold gets 'unrounded'</li>
+          <li>
+            <!-- JAL-2982 -->PCA image export doesn't respect background colour</li>
+          <li>
+            <!-- JAL-2963 -->PCA points don't dim when rotated about y axis</li>
+          <li>
+            <!-- JAL-2959 -->PCA Print dialog continues after Cancel</li>
+          <li>
+            <!-- JAL-3078 -->Cancel in Tree Font dialog resets alignment, not Tree font</li>
+          <li>
+            <!-- JAL-2964 -->Associate Tree with All Views not restored from project file</li>
+          <li>
+            <!-- JAL-2915 -->Scrolling of split frame is sluggish if Overview shown in complementary view</li>
+          <li>
+            <!-- JAL-2898 -->stop_gained variants not shown correctly on peptide sequence</li>
+          <li>
+            <!-- JAL-914 -->Help page can be opened twice</li>
         </ul>
         <em>Editing</em>
         <ul>
           <li>
             <!-- JAL-2822 -->Start and End should be updated when
             sequence data at beginning or end of alignment added/removed
-            via 'Edit' sequence
-          </li>
+            via 'Edit' sequence</li>
           <li>
             <!-- JAL-2541 -->Delete/Cut selection doesn't relocate
             sequence features correctly when start of sequence is
-            removed (Known defect since 2.10)
-          </li>
-          <li>
-            <!-- JAL- -->
-          </li>
+            removed (Known defect since 2.10)</li>
         </ul>
         <em>New Known Defects</em>
         <ul>
           <li>
-            <!-- JAL-3178 -->Nonpositional features lose feature group
-            on export as jalview features file
-          </li>
+            <!-- JAL-2647 -->Input Data menu entry is greyed out when PCA View is restored from a Jalview 2.11 project</li> 
+          <li>
+            <!-- JAL-3213 -->Alignment panel height can be too small after 'New View'</li> 
+          <li>
+            <!-- JAL-3240 -->Display is incorrect after removing gapped columns within hidden columns</li> 
         </ul>
-      </div></td>
+      </td>
     </tr>
     <tr>
     <td width="60" nowrap>
diff --git a/help/help/html/whatsNew.html b/help/help/html/whatsNew.html
new file mode 100755 (executable)
index 0000000..3538ff4
--- /dev/null
@@ -0,0 +1,71 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ 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>What's new ?</title>
+</head>
+<body>
+  <p>
+    <strong>Jalview 2.11 - major and minor new features</strong>
+  </p>
+  <p>Jalview 2.11 introduces support for loading VCF files, and new
+    filters and shading models for sequence features. Under the hood,
+    we've addressed many bugs, and also made some important changes in
+    the way the Jalview desktop is installed and launched.</p>
+  <ul>
+    <li><em>VCF Support</em>. Proteins and genomic contigs with
+      chromosomal location annotation (such as protein coding genes
+      retrieved from Ensembl) can be annotated with variants imported
+      from a local VCF file.</li>
+    <li><em>The Jalview Launcher and Update System</em><br />
+      Jalview's new installation model means you'll only need to
+      download and install Jalview once. After installation, Jalview
+      will automatically keep itself up to date. The launcher also sets
+      Jalview's memory automatically, so you'll never again have to
+      manually configure Java's memory settings.<br />We are grateful to
+      Install4J who provided us with a free license for their
+      installation system, and Jalview's over the air update system is
+      via Getdown.</li>
+  </ul>
+  <p>
+    The full list of bugs fixed in this release can be found in the <a
+      href="releases.html#Jalview.2.11">2.11 Release Notes</a>.
+  </p>
+  <p>
+    <strong>Jalview and Java 11, 13, and onwards</strong>
+  </p>
+  <p>Java 11 provides improved performance and better OS
+    integration, so we now recommend users select our Java 11 Jalview
+    distribution rather than the legacy Java 8 build.</p>
+  <em>Known Issues - update for 211 </em>
+  <ul>
+    <li>OSX: The 'Open File' dialog for Jalview's Groovy Console
+      appears with the title 'Save As', and attempting to select a file
+      to load yields a FileNotFound exception.</br>The workaround is to first
+      clear the 'Untitled' filename before selecting the file you wish
+      to load.
+    </li>
+    <li>OSX: Links don't open when clicked on or via the Sequence
+      or Alignment window popup menu.</li>
+    <li>OSX (Webstart): Jalview only displays old news feed items</li>
+  </ul>
+</body>
+</html>
similarity index 100%
rename from help/icons/Home.png
rename to help/help/icons/Home.png
similarity index 100%
rename from help/icons/back.png
rename to help/help/icons/back.png
diff --git a/help/html/whatsNew.html b/help/html/whatsNew.html
deleted file mode 100755 (executable)
index 13fe656..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<html>
-<!--
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ 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>What's new ?</title>
-</head>
-<body>
-  <p>
-    <strong>What's new in Jalview 2.10.5 ?</strong>
-  </p>
-  <p>Jalview 2.10.5 is a minor release that includes critical
-    patches for users working with Ensembl, RNA secondary structure
-    annotation, and those running Jalview on OSX with Java 10.</p>
-  <ul>
-    <li>Jalview's default memory limit increased to 1G. <br/>If you have
-      problems starting Jalview 2.10.5 and you have 1G or less
-      physical memory on your machine, you will need to <a
-      href="memory.html#memsetting">reduce the memory</a> allocated to
-      Jalview.
-    </li>
-    <li>EPS, PNG and SVG export now includes hidden sequence
-      markers, and representative sequences are marked in bold.</li>
-    <li>Ensembl Client updated for Ensembl Rest API v7.<br />The
-      latest Ensembl API is not backwards compatible with earlier
-      versions of Jalview, so if you require Ensembl functionality you
-      will need to install this release.
-    </li>
-    <li>Improved support for VIENNA extended dot-bracket notation
-      for RNA secondary structure.</li>
-    <li>Positional and selected region highlighting in VARNA
-      'trimmed sequence' view made more reliable.</li>
-  </ul>
-  <p>
-    The full list of bugs fixed in this release can be found in the <a
-      href="releases.html#Jalview.2.10.5">2.10.5 Release Notes</a>. The
-    majority of bug fixes and improvements in 2.10.5 are due to Jalview users
-    contacting us via the jalview-discuss email list. Thanks to everyone
-    who took the time to help make Jalview better !
-  </p>
-  <p>
-    <strong>Jalview and Java 10</strong>
-  </p>
-  <p>This release addresses a critical bug for OSX users who are
-    running Jalview with Java 10 which can prevent files being saved
-    correctly through the 'Save As' dialog box.</p>
-    <em>Known Issues</em>
-  <ul>
-    <li>OSX: The 'Open File' dialog for Jalview's Groovy Console
-      appears with the title 'Save As', and attempting to select a file to load yields a FileNotFound exception.</br>The workaround is to first clear the 'Untitled' filename before selecting the file you wish to load.
-      </li>
-    <li>OSX: Links don't open when clicked on or via the Sequence or Alignment window popup menu.</li>
-    <li>OSX (Webstart): Jalview only displays old news feed items</li>
-  </ul>
-</body>
-</html>
diff --git a/j11lib/FastInfoset.jar b/j11lib/FastInfoset.jar
new file mode 100644 (file)
index 0000000..2671209
Binary files /dev/null and b/j11lib/FastInfoset.jar differ
diff --git a/j11lib/JGoogleAnalytics_0.3.jar b/j11lib/JGoogleAnalytics_0.3.jar
new file mode 100644 (file)
index 0000000..0dbc98c
Binary files /dev/null and b/j11lib/JGoogleAnalytics_0.3.jar differ
diff --git a/j11lib/Jmol-14.6.4_2016.10.26-no_netscape.jar b/j11lib/Jmol-14.6.4_2016.10.26-no_netscape.jar
new file mode 100644 (file)
index 0000000..f214bb4
Binary files /dev/null and b/j11lib/Jmol-14.6.4_2016.10.26-no_netscape.jar differ
diff --git a/j11lib/VARNAv3-93.jar b/j11lib/VARNAv3-93.jar
new file mode 100644 (file)
index 0000000..9d41f6b
Binary files /dev/null and b/j11lib/VARNAv3-93.jar differ
diff --git a/j11lib/VAqua5-patch.jar b/j11lib/VAqua5-patch.jar
new file mode 100644 (file)
index 0000000..7b5c27b
Binary files /dev/null and b/j11lib/VAqua5-patch.jar differ
diff --git a/j11lib/apache-mime4j-0.6.jar b/j11lib/apache-mime4j-0.6.jar
new file mode 100644 (file)
index 0000000..1d2282c
Binary files /dev/null and b/j11lib/apache-mime4j-0.6.jar differ
diff --git a/j11lib/axis.jar b/j11lib/axis.jar
new file mode 100755 (executable)
index 0000000..90bb798
Binary files /dev/null and b/j11lib/axis.jar differ
diff --git a/j11lib/biojava-core-4.1.0.jar b/j11lib/biojava-core-4.1.0.jar
new file mode 100644 (file)
index 0000000..5a09c1f
Binary files /dev/null and b/j11lib/biojava-core-4.1.0.jar differ
diff --git a/j11lib/biojava-ontology-4.1.0.jar b/j11lib/biojava-ontology-4.1.0.jar
new file mode 100644 (file)
index 0000000..80737d5
Binary files /dev/null and b/j11lib/biojava-ontology-4.1.0.jar differ
diff --git a/j11lib/commons-codec-1.3.jar b/j11lib/commons-codec-1.3.jar
new file mode 100644 (file)
index 0000000..957b675
Binary files /dev/null and b/j11lib/commons-codec-1.3.jar differ
diff --git a/j11lib/commons-compress-1.18.jar b/j11lib/commons-compress-1.18.jar
new file mode 100644 (file)
index 0000000..e401046
Binary files /dev/null and b/j11lib/commons-compress-1.18.jar differ
diff --git a/j11lib/commons-discovery.jar b/j11lib/commons-discovery.jar
new file mode 100755 (executable)
index 0000000..b885548
Binary files /dev/null and b/j11lib/commons-discovery.jar differ
diff --git a/j11lib/commons-logging-1.1.1.jar b/j11lib/commons-logging-1.1.1.jar
new file mode 100644 (file)
index 0000000..1deef14
Binary files /dev/null and b/j11lib/commons-logging-1.1.1.jar differ
diff --git a/j11lib/getdown-core.jar b/j11lib/getdown-core.jar
new file mode 100644 (file)
index 0000000..7230883
Binary files /dev/null and b/j11lib/getdown-core.jar differ
diff --git a/j11lib/gmbal-api-only-MODULE.jar b/j11lib/gmbal-api-only-MODULE.jar
new file mode 100644 (file)
index 0000000..06a5fcc
Binary files /dev/null and b/j11lib/gmbal-api-only-MODULE.jar differ
diff --git a/j11lib/groovy-2.5.6.jar b/j11lib/groovy-2.5.6.jar
new file mode 100644 (file)
index 0000000..3a4edfa
Binary files /dev/null and b/j11lib/groovy-2.5.6.jar differ
diff --git a/j11lib/groovy-console-2.5.6.jar b/j11lib/groovy-console-2.5.6.jar
new file mode 100644 (file)
index 0000000..e18e426
Binary files /dev/null and b/j11lib/groovy-console-2.5.6.jar differ
diff --git a/j11lib/htsjdk-2.12.0.jar b/j11lib/htsjdk-2.12.0.jar
new file mode 100644 (file)
index 0000000..1df12b2
Binary files /dev/null and b/j11lib/htsjdk-2.12.0.jar differ
diff --git a/j11lib/httpclient-4.0.3.jar b/j11lib/httpclient-4.0.3.jar
new file mode 100644 (file)
index 0000000..fd0d377
Binary files /dev/null and b/j11lib/httpclient-4.0.3.jar differ
diff --git a/j11lib/httpcore-4.0.1.jar b/j11lib/httpcore-4.0.1.jar
new file mode 100644 (file)
index 0000000..4aef35e
Binary files /dev/null and b/j11lib/httpcore-4.0.1.jar differ
diff --git a/j11lib/httpmime-4.0.3.jar b/j11lib/httpmime-4.0.3.jar
new file mode 100644 (file)
index 0000000..0dfd331
Binary files /dev/null and b/j11lib/httpmime-4.0.3.jar differ
diff --git a/j11lib/i4jruntime.jar b/j11lib/i4jruntime.jar
new file mode 100644 (file)
index 0000000..4be2a73
Binary files /dev/null and b/j11lib/i4jruntime.jar differ
diff --git a/j11lib/istack-commons-runtime.jar b/j11lib/istack-commons-runtime.jar
new file mode 100644 (file)
index 0000000..2fe5b82
Binary files /dev/null and b/j11lib/istack-commons-runtime.jar differ
diff --git a/j11lib/jabaws-min-client-2.2.0.jar b/j11lib/jabaws-min-client-2.2.0.jar
new file mode 100644 (file)
index 0000000..37426c3
Binary files /dev/null and b/j11lib/jabaws-min-client-2.2.0.jar differ
diff --git a/j11lib/java-json.jar b/j11lib/java-json.jar
new file mode 100755 (executable)
index 0000000..2f211e3
Binary files /dev/null and b/j11lib/java-json.jar differ
diff --git a/j11lib/javax.activation-MODULE.jar b/j11lib/javax.activation-MODULE.jar
new file mode 100644 (file)
index 0000000..b764265
Binary files /dev/null and b/j11lib/javax.activation-MODULE.jar differ
diff --git a/j11lib/javax.annotation-api-MODULE.jar b/j11lib/javax.annotation-api-MODULE.jar
new file mode 100644 (file)
index 0000000..a0f12ad
Binary files /dev/null and b/j11lib/javax.annotation-api-MODULE.jar differ
diff --git a/j11lib/javax.jws-api-1.1.jar b/j11lib/javax.jws-api-1.1.jar
new file mode 100644 (file)
index 0000000..d4c06d2
Binary files /dev/null and b/j11lib/javax.jws-api-1.1.jar differ
diff --git a/j11lib/javax.servlet-api-MODULE.jar b/j11lib/javax.servlet-api-MODULE.jar
new file mode 100644 (file)
index 0000000..9241563
Binary files /dev/null and b/j11lib/javax.servlet-api-MODULE.jar differ
diff --git a/j11lib/javax.xml.rpc-api-1.1.2.jar b/j11lib/javax.xml.rpc-api-1.1.2.jar
new file mode 100644 (file)
index 0000000..61ac294
Binary files /dev/null and b/j11lib/javax.xml.rpc-api-1.1.2.jar differ
diff --git a/j11lib/javax.xml.soap-api.jar b/j11lib/javax.xml.soap-api.jar
new file mode 100644 (file)
index 0000000..c47a3b0
Binary files /dev/null and b/j11lib/javax.xml.soap-api.jar differ
diff --git a/j11lib/jaxb-api-java9.jar b/j11lib/jaxb-api-java9.jar
new file mode 100644 (file)
index 0000000..41de3c1
Binary files /dev/null and b/j11lib/jaxb-api-java9.jar differ
diff --git a/j11lib/jaxb-runtime.jar b/j11lib/jaxb-runtime.jar
new file mode 100644 (file)
index 0000000..0b9ef67
Binary files /dev/null and b/j11lib/jaxb-runtime.jar differ
diff --git a/j11lib/jaxws-api.jar b/j11lib/jaxws-api.jar
new file mode 100644 (file)
index 0000000..806c0e1
Binary files /dev/null and b/j11lib/jaxws-api.jar differ
diff --git a/j11lib/jaxws-rt-java9.jar b/j11lib/jaxws-rt-java9.jar
new file mode 100644 (file)
index 0000000..f380d18
Binary files /dev/null and b/j11lib/jaxws-rt-java9.jar differ
diff --git a/j11lib/jersey-client-1.19.1.jar b/j11lib/jersey-client-1.19.1.jar
new file mode 100644 (file)
index 0000000..455a7f2
Binary files /dev/null and b/j11lib/jersey-client-1.19.1.jar differ
diff --git a/j11lib/jersey-core-1.19.1.jar b/j11lib/jersey-core-1.19.1.jar
new file mode 100644 (file)
index 0000000..8e5185d
Binary files /dev/null and b/j11lib/jersey-core-1.19.1.jar differ
diff --git a/j11lib/jersey-json-1.19.1.jar b/j11lib/jersey-json-1.19.1.jar
new file mode 100644 (file)
index 0000000..2cbf79e
Binary files /dev/null and b/j11lib/jersey-json-1.19.1.jar differ
diff --git a/j11lib/jetty-http-9.2.10.v20150310.jar b/j11lib/jetty-http-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..15aff51
Binary files /dev/null and b/j11lib/jetty-http-9.2.10.v20150310.jar differ
diff --git a/j11lib/jetty-io-9.2.10.v20150310.jar b/j11lib/jetty-io-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..56cee2c
Binary files /dev/null and b/j11lib/jetty-io-9.2.10.v20150310.jar differ
diff --git a/j11lib/jetty-server-9.2.10.v20150310.jar b/j11lib/jetty-server-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..815cb08
Binary files /dev/null and b/j11lib/jetty-server-9.2.10.v20150310.jar differ
diff --git a/j11lib/jetty-util-9.2.10.v20150310.jar b/j11lib/jetty-util-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..fe27758
Binary files /dev/null and b/j11lib/jetty-util-9.2.10.v20150310.jar differ
diff --git a/j11lib/jfreesvg-2.1.jar b/j11lib/jfreesvg-2.1.jar
new file mode 100644 (file)
index 0000000..91d453c
Binary files /dev/null and b/j11lib/jfreesvg-2.1.jar differ
diff --git a/j11lib/jhall.jar b/j11lib/jhall.jar
new file mode 100755 (executable)
index 0000000..ff08af0
Binary files /dev/null and b/j11lib/jhall.jar differ
diff --git a/j11lib/json_simple-1.1.jar b/j11lib/json_simple-1.1.jar
new file mode 100644 (file)
index 0000000..f395f41
Binary files /dev/null and b/j11lib/json_simple-1.1.jar differ
diff --git a/j11lib/jsoup-1.8.1.jar b/j11lib/jsoup-1.8.1.jar
new file mode 100644 (file)
index 0000000..ae717d4
Binary files /dev/null and b/j11lib/jsoup-1.8.1.jar differ
diff --git a/j11lib/jswingreader-0.3.jar b/j11lib/jswingreader-0.3.jar
new file mode 100644 (file)
index 0000000..c632130
Binary files /dev/null and b/j11lib/jswingreader-0.3.jar differ
diff --git a/j11lib/libquaqua-8.0.jnilib.jar b/j11lib/libquaqua-8.0.jnilib.jar
new file mode 100644 (file)
index 0000000..79383b0
Binary files /dev/null and b/j11lib/libquaqua-8.0.jnilib.jar differ
diff --git a/j11lib/libquaqua64-8.0.jnilib.jar b/j11lib/libquaqua64-8.0.jnilib.jar
new file mode 100644 (file)
index 0000000..efb46a4
Binary files /dev/null and b/j11lib/libquaqua64-8.0.jnilib.jar differ
diff --git a/j11lib/log4j-to-slf4j-2.0-rc2.jar b/j11lib/log4j-to-slf4j-2.0-rc2.jar
new file mode 100644 (file)
index 0000000..4bbf54a
Binary files /dev/null and b/j11lib/log4j-to-slf4j-2.0-rc2.jar differ
diff --git a/j11lib/mail-MODULE.jar b/j11lib/mail-MODULE.jar
new file mode 100644 (file)
index 0000000..367ca9e
Binary files /dev/null and b/j11lib/mail-MODULE.jar differ
diff --git a/j11lib/miglayout-4.0-swing.jar b/j11lib/miglayout-4.0-swing.jar
new file mode 100644 (file)
index 0000000..7b6a22c
Binary files /dev/null and b/j11lib/miglayout-4.0-swing.jar differ
diff --git a/j11lib/mimepull.jar b/j11lib/mimepull.jar
new file mode 100644 (file)
index 0000000..9007c56
Binary files /dev/null and b/j11lib/mimepull.jar differ
diff --git a/j11lib/policy.jar b/j11lib/policy.jar
new file mode 100644 (file)
index 0000000..8a2ef0f
Binary files /dev/null and b/j11lib/policy.jar differ
diff --git a/j11lib/quaqua-filechooser-only-8.0.jar b/j11lib/quaqua-filechooser-only-8.0.jar
new file mode 100644 (file)
index 0000000..182e0da
Binary files /dev/null and b/j11lib/quaqua-filechooser-only-8.0.jar differ
diff --git a/j11lib/regex.jar b/j11lib/regex.jar
new file mode 100755 (executable)
index 0000000..3e29d22
Binary files /dev/null and b/j11lib/regex.jar differ
diff --git a/j11lib/saaj-impl.jar b/j11lib/saaj-impl.jar
new file mode 100644 (file)
index 0000000..7eda25c
Binary files /dev/null and b/j11lib/saaj-impl.jar differ
diff --git a/j11lib/slf4j-api-1.7.7.jar b/j11lib/slf4j-api-1.7.7.jar
new file mode 100644 (file)
index 0000000..b28e220
Binary files /dev/null and b/j11lib/slf4j-api-1.7.7.jar differ
diff --git a/j11lib/slf4j-log4j12-1.7.7.jar b/j11lib/slf4j-log4j12-1.7.7.jar
new file mode 100644 (file)
index 0000000..12c804e
Binary files /dev/null and b/j11lib/slf4j-log4j12-1.7.7.jar differ
diff --git a/j11lib/stax-ex.jar b/j11lib/stax-ex.jar
new file mode 100644 (file)
index 0000000..5e60637
Binary files /dev/null and b/j11lib/stax-ex.jar differ
diff --git a/j11lib/stax2-api-MODULE.jar b/j11lib/stax2-api-MODULE.jar
new file mode 100644 (file)
index 0000000..eff6586
Binary files /dev/null and b/j11lib/stax2-api-MODULE.jar differ
diff --git a/j11lib/streambuffer.jar b/j11lib/streambuffer.jar
new file mode 100644 (file)
index 0000000..a8f9640
Binary files /dev/null and b/j11lib/streambuffer.jar differ
diff --git a/j11lib/txw2.jar b/j11lib/txw2.jar
new file mode 100644 (file)
index 0000000..75ed519
Binary files /dev/null and b/j11lib/txw2.jar differ
diff --git a/j11lib/vamsas-client.jar b/j11lib/vamsas-client.jar
new file mode 100644 (file)
index 0000000..2634954
Binary files /dev/null and b/j11lib/vamsas-client.jar differ
diff --git a/j11lib/wsdl4j-MODULE.jar b/j11lib/wsdl4j-MODULE.jar
new file mode 100644 (file)
index 0000000..0f9e45c
Binary files /dev/null and b/j11lib/wsdl4j-MODULE.jar differ
diff --git a/j11lib/xercesImpl.jar b/j11lib/xercesImpl.jar
new file mode 100644 (file)
index 0000000..0aaa990
Binary files /dev/null and b/j11lib/xercesImpl.jar differ
diff --git a/j11mod/FastInfoset.jar b/j11mod/FastInfoset.jar
new file mode 100644 (file)
index 0000000..2671209
Binary files /dev/null and b/j11mod/FastInfoset.jar differ
diff --git a/j11mod/getdown-launcher.jar b/j11mod/getdown-launcher.jar
new file mode 100644 (file)
index 0000000..187694e
Binary files /dev/null and b/j11mod/getdown-launcher.jar differ
diff --git a/j11mod/istack-commons-runtime.jar b/j11mod/istack-commons-runtime.jar
new file mode 100644 (file)
index 0000000..2fe5b82
Binary files /dev/null and b/j11mod/istack-commons-runtime.jar differ
diff --git a/j11mod/javax.activation-MODULE.jar b/j11mod/javax.activation-MODULE.jar
new file mode 100644 (file)
index 0000000..b764265
Binary files /dev/null and b/j11mod/javax.activation-MODULE.jar differ
diff --git a/j11mod/javax.annotation-api-MODULE.jar b/j11mod/javax.annotation-api-MODULE.jar
new file mode 100644 (file)
index 0000000..a0f12ad
Binary files /dev/null and b/j11mod/javax.annotation-api-MODULE.jar differ
diff --git a/j11mod/javax.jws-api-1.1.jar b/j11mod/javax.jws-api-1.1.jar
new file mode 100644 (file)
index 0000000..d4c06d2
Binary files /dev/null and b/j11mod/javax.jws-api-1.1.jar differ
diff --git a/j11mod/javax.servlet-api-MODULE.jar b/j11mod/javax.servlet-api-MODULE.jar
new file mode 100644 (file)
index 0000000..9241563
Binary files /dev/null and b/j11mod/javax.servlet-api-MODULE.jar differ
diff --git a/j11mod/javax.ws.rs-api-2.1.1.jar b/j11mod/javax.ws.rs-api-2.1.1.jar
new file mode 100644 (file)
index 0000000..3eabbf0
Binary files /dev/null and b/j11mod/javax.ws.rs-api-2.1.1.jar differ
diff --git a/j11mod/javax.xml.rpc-api-1.1.2.jar b/j11mod/javax.xml.rpc-api-1.1.2.jar
new file mode 100644 (file)
index 0000000..61ac294
Binary files /dev/null and b/j11mod/javax.xml.rpc-api-1.1.2.jar differ
diff --git a/j11mod/javax.xml.soap-api.jar b/j11mod/javax.xml.soap-api.jar
new file mode 100644 (file)
index 0000000..c47a3b0
Binary files /dev/null and b/j11mod/javax.xml.soap-api.jar differ
diff --git a/j11mod/jaxb-api-java9.jar b/j11mod/jaxb-api-java9.jar
new file mode 100644 (file)
index 0000000..41de3c1
Binary files /dev/null and b/j11mod/jaxb-api-java9.jar differ
diff --git a/j11mod/jaxb-runtime.jar b/j11mod/jaxb-runtime.jar
new file mode 100644 (file)
index 0000000..0b9ef67
Binary files /dev/null and b/j11mod/jaxb-runtime.jar differ
diff --git a/j11mod/jaxws-api.jar b/j11mod/jaxws-api.jar
new file mode 100644 (file)
index 0000000..806c0e1
Binary files /dev/null and b/j11mod/jaxws-api.jar differ
diff --git a/j11mod/mimepull.jar b/j11mod/mimepull.jar
new file mode 100644 (file)
index 0000000..9007c56
Binary files /dev/null and b/j11mod/mimepull.jar differ
diff --git a/j11mod/policy.jar b/j11mod/policy.jar
new file mode 100644 (file)
index 0000000..8a2ef0f
Binary files /dev/null and b/j11mod/policy.jar differ
diff --git a/j11mod/saaj-impl.jar b/j11mod/saaj-impl.jar
new file mode 100644 (file)
index 0000000..7eda25c
Binary files /dev/null and b/j11mod/saaj-impl.jar differ
diff --git a/j11mod/stax-ex.jar b/j11mod/stax-ex.jar
new file mode 100644 (file)
index 0000000..5e60637
Binary files /dev/null and b/j11mod/stax-ex.jar differ
diff --git a/j11mod/stax2-api-MODULE.jar b/j11mod/stax2-api-MODULE.jar
new file mode 100644 (file)
index 0000000..eff6586
Binary files /dev/null and b/j11mod/stax2-api-MODULE.jar differ
diff --git a/j11mod/streambuffer.jar b/j11mod/streambuffer.jar
new file mode 100644 (file)
index 0000000..a8f9640
Binary files /dev/null and b/j11mod/streambuffer.jar differ
diff --git a/j11mod/txw2.jar b/j11mod/txw2.jar
new file mode 100644 (file)
index 0000000..75ed519
Binary files /dev/null and b/j11mod/txw2.jar differ
diff --git a/j11mod/wsdl4j-MODULE.jar b/j11mod/wsdl4j-MODULE.jar
new file mode 100644 (file)
index 0000000..0f9e45c
Binary files /dev/null and b/j11mod/wsdl4j-MODULE.jar differ
diff --git a/j8lib/JGoogleAnalytics_0.3.jar b/j8lib/JGoogleAnalytics_0.3.jar
new file mode 100644 (file)
index 0000000..0dbc98c
Binary files /dev/null and b/j8lib/JGoogleAnalytics_0.3.jar differ
diff --git a/j8lib/VARNAv3-93.jar b/j8lib/VARNAv3-93.jar
new file mode 100644 (file)
index 0000000..9d41f6b
Binary files /dev/null and b/j8lib/VARNAv3-93.jar differ
diff --git a/j8lib/VAqua5-patch.jar b/j8lib/VAqua5-patch.jar
new file mode 100644 (file)
index 0000000..7b5c27b
Binary files /dev/null and b/j8lib/VAqua5-patch.jar differ
similarity index 100%
rename from lib/activation.jar
rename to j8lib/activation.jar
diff --git a/j8lib/apache-mime4j-0.6.jar b/j8lib/apache-mime4j-0.6.jar
new file mode 100644 (file)
index 0000000..1d2282c
Binary files /dev/null and b/j8lib/apache-mime4j-0.6.jar differ
diff --git a/j8lib/axis.jar b/j8lib/axis.jar
new file mode 100755 (executable)
index 0000000..90bb798
Binary files /dev/null and b/j8lib/axis.jar differ
diff --git a/j8lib/biojava-core-4.1.0.jar b/j8lib/biojava-core-4.1.0.jar
new file mode 100644 (file)
index 0000000..5a09c1f
Binary files /dev/null and b/j8lib/biojava-core-4.1.0.jar differ
diff --git a/j8lib/biojava-ontology-4.1.0.jar b/j8lib/biojava-ontology-4.1.0.jar
new file mode 100644 (file)
index 0000000..80737d5
Binary files /dev/null and b/j8lib/biojava-ontology-4.1.0.jar differ
diff --git a/j8lib/commons-codec-1.3.jar b/j8lib/commons-codec-1.3.jar
new file mode 100644 (file)
index 0000000..957b675
Binary files /dev/null and b/j8lib/commons-codec-1.3.jar differ
diff --git a/j8lib/commons-compress-1.18.jar b/j8lib/commons-compress-1.18.jar
new file mode 100644 (file)
index 0000000..e401046
Binary files /dev/null and b/j8lib/commons-compress-1.18.jar differ
diff --git a/j8lib/commons-discovery.jar b/j8lib/commons-discovery.jar
new file mode 100755 (executable)
index 0000000..b885548
Binary files /dev/null and b/j8lib/commons-discovery.jar differ
diff --git a/j8lib/commons-logging-1.1.1.jar b/j8lib/commons-logging-1.1.1.jar
new file mode 100644 (file)
index 0000000..1deef14
Binary files /dev/null and b/j8lib/commons-logging-1.1.1.jar differ
diff --git a/j8lib/getdown-core.jar b/j8lib/getdown-core.jar
new file mode 100644 (file)
index 0000000..7230883
Binary files /dev/null and b/j8lib/getdown-core.jar differ
diff --git a/j8lib/htsjdk-2.12.0.jar b/j8lib/htsjdk-2.12.0.jar
new file mode 100644 (file)
index 0000000..1df12b2
Binary files /dev/null and b/j8lib/htsjdk-2.12.0.jar differ
diff --git a/j8lib/httpclient-4.0.3.jar b/j8lib/httpclient-4.0.3.jar
new file mode 100644 (file)
index 0000000..fd0d377
Binary files /dev/null and b/j8lib/httpclient-4.0.3.jar differ
diff --git a/j8lib/httpcore-4.0.1.jar b/j8lib/httpcore-4.0.1.jar
new file mode 100644 (file)
index 0000000..4aef35e
Binary files /dev/null and b/j8lib/httpcore-4.0.1.jar differ
diff --git a/j8lib/httpmime-4.0.3.jar b/j8lib/httpmime-4.0.3.jar
new file mode 100644 (file)
index 0000000..0dfd331
Binary files /dev/null and b/j8lib/httpmime-4.0.3.jar differ
diff --git a/j8lib/i4jruntime.jar b/j8lib/i4jruntime.jar
new file mode 100644 (file)
index 0000000..4be2a73
Binary files /dev/null and b/j8lib/i4jruntime.jar differ
diff --git a/j8lib/intervalstore-v1.0.jar b/j8lib/intervalstore-v1.0.jar
new file mode 100644 (file)
index 0000000..4f6101c
Binary files /dev/null and b/j8lib/intervalstore-v1.0.jar differ
diff --git a/j8lib/jabaws-min-client-2.2.0.jar b/j8lib/jabaws-min-client-2.2.0.jar
new file mode 100644 (file)
index 0000000..37426c3
Binary files /dev/null and b/j8lib/jabaws-min-client-2.2.0.jar differ
diff --git a/j8lib/java-json.jar b/j8lib/java-json.jar
new file mode 100755 (executable)
index 0000000..2f211e3
Binary files /dev/null and b/j8lib/java-json.jar differ
diff --git a/j8lib/jaxrpc.jar b/j8lib/jaxrpc.jar
new file mode 100755 (executable)
index 0000000..ebd457b
Binary files /dev/null and b/j8lib/jaxrpc.jar differ
diff --git a/j8lib/jersey-client-1.19.jar b/j8lib/jersey-client-1.19.jar
new file mode 100644 (file)
index 0000000..c9e0f56
Binary files /dev/null and b/j8lib/jersey-client-1.19.jar differ
diff --git a/j8lib/jersey-core-1.19.jar b/j8lib/jersey-core-1.19.jar
new file mode 100644 (file)
index 0000000..92feb63
Binary files /dev/null and b/j8lib/jersey-core-1.19.jar differ
diff --git a/j8lib/jersey-json-1.19.jar b/j8lib/jersey-json-1.19.jar
new file mode 100644 (file)
index 0000000..b609411
Binary files /dev/null and b/j8lib/jersey-json-1.19.jar differ
diff --git a/j8lib/jetty-http-9.2.10.v20150310.jar b/j8lib/jetty-http-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..15aff51
Binary files /dev/null and b/j8lib/jetty-http-9.2.10.v20150310.jar differ
diff --git a/j8lib/jetty-io-9.2.10.v20150310.jar b/j8lib/jetty-io-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..56cee2c
Binary files /dev/null and b/j8lib/jetty-io-9.2.10.v20150310.jar differ
diff --git a/j8lib/jetty-server-9.2.10.v20150310.jar b/j8lib/jetty-server-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..815cb08
Binary files /dev/null and b/j8lib/jetty-server-9.2.10.v20150310.jar differ
diff --git a/j8lib/jetty-util-9.2.10.v20150310.jar b/j8lib/jetty-util-9.2.10.v20150310.jar
new file mode 100644 (file)
index 0000000..fe27758
Binary files /dev/null and b/j8lib/jetty-util-9.2.10.v20150310.jar differ
diff --git a/j8lib/jfreesvg-2.1.jar b/j8lib/jfreesvg-2.1.jar
new file mode 100644 (file)
index 0000000..91d453c
Binary files /dev/null and b/j8lib/jfreesvg-2.1.jar differ
diff --git a/j8lib/jhall.jar b/j8lib/jhall.jar
new file mode 100755 (executable)
index 0000000..ff08af0
Binary files /dev/null and b/j8lib/jhall.jar differ
diff --git a/j8lib/json_simple-1.1.jar b/j8lib/json_simple-1.1.jar
new file mode 100644 (file)
index 0000000..f395f41
Binary files /dev/null and b/j8lib/json_simple-1.1.jar differ
diff --git a/j8lib/jsoup-1.8.1.jar b/j8lib/jsoup-1.8.1.jar
new file mode 100644 (file)
index 0000000..ae717d4
Binary files /dev/null and b/j8lib/jsoup-1.8.1.jar differ
diff --git a/j8lib/jsr311-api-1.1.1.jar b/j8lib/jsr311-api-1.1.1.jar
new file mode 100644 (file)
index 0000000..ec8bc81
Binary files /dev/null and b/j8lib/jsr311-api-1.1.1.jar differ
diff --git a/j8lib/jswingreader-0.3.jar b/j8lib/jswingreader-0.3.jar
new file mode 100644 (file)
index 0000000..c632130
Binary files /dev/null and b/j8lib/jswingreader-0.3.jar differ
diff --git a/j8lib/libquaqua-8.0.jnilib.jar b/j8lib/libquaqua-8.0.jnilib.jar
new file mode 100644 (file)
index 0000000..79383b0
Binary files /dev/null and b/j8lib/libquaqua-8.0.jnilib.jar differ
diff --git a/j8lib/libquaqua64-8.0.jnilib.jar b/j8lib/libquaqua64-8.0.jnilib.jar
new file mode 100644 (file)
index 0000000..efb46a4
Binary files /dev/null and b/j8lib/libquaqua64-8.0.jnilib.jar differ
diff --git a/j8lib/log4j-to-slf4j-2.0-rc2.jar b/j8lib/log4j-to-slf4j-2.0-rc2.jar
new file mode 100644 (file)
index 0000000..4bbf54a
Binary files /dev/null and b/j8lib/log4j-to-slf4j-2.0-rc2.jar differ
similarity index 100%
rename from lib/mail.jar
rename to j8lib/mail.jar
diff --git a/j8lib/miglayout-4.0-swing.jar b/j8lib/miglayout-4.0-swing.jar
new file mode 100644 (file)
index 0000000..7b6a22c
Binary files /dev/null and b/j8lib/miglayout-4.0-swing.jar differ
diff --git a/j8lib/quaqua-filechooser-only-8.0.jar b/j8lib/quaqua-filechooser-only-8.0.jar
new file mode 100644 (file)
index 0000000..182e0da
Binary files /dev/null and b/j8lib/quaqua-filechooser-only-8.0.jar differ
diff --git a/j8lib/regex.jar b/j8lib/regex.jar
new file mode 100755 (executable)
index 0000000..3e29d22
Binary files /dev/null and b/j8lib/regex.jar differ
similarity index 100%
rename from lib/saaj.jar
rename to j8lib/saaj.jar
diff --git a/j8lib/slf4j-api-1.7.7.jar b/j8lib/slf4j-api-1.7.7.jar
new file mode 100644 (file)
index 0000000..b28e220
Binary files /dev/null and b/j8lib/slf4j-api-1.7.7.jar differ
diff --git a/j8lib/slf4j-log4j12-1.7.7.jar b/j8lib/slf4j-log4j12-1.7.7.jar
new file mode 100644 (file)
index 0000000..12c804e
Binary files /dev/null and b/j8lib/slf4j-log4j12-1.7.7.jar differ
diff --git a/j8lib/vamsas-client.jar b/j8lib/vamsas-client.jar
new file mode 100644 (file)
index 0000000..2634954
Binary files /dev/null and b/j8lib/vamsas-client.jar differ
similarity index 100%
rename from lib/wsdl4j.jar
rename to j8lib/wsdl4j.jar
diff --git a/j8lib/xercesImpl.jar b/j8lib/xercesImpl.jar
new file mode 100644 (file)
index 0000000..0aaa990
Binary files /dev/null and b/j8lib/xercesImpl.jar differ
similarity index 100%
rename from lib/xml-apis.jar
rename to j8lib/xml-apis.jar
diff --git a/lib/Jmol-14.6.4_2016.10.26-no_netscape.jar b/lib/Jmol-14.6.4_2016.10.26-no_netscape.jar
new file mode 100644 (file)
index 0000000..f214bb4
Binary files /dev/null and b/lib/Jmol-14.6.4_2016.10.26-no_netscape.jar differ
diff --git a/lib/commons-compress-1.18.jar b/lib/commons-compress-1.18.jar
new file mode 100644 (file)
index 0000000..e401046
Binary files /dev/null and b/lib/commons-compress-1.18.jar differ
diff --git a/lib/getdown-core.jar b/lib/getdown-core.jar
new file mode 100644 (file)
index 0000000..3e811b2
Binary files /dev/null and b/lib/getdown-core.jar differ
diff --git a/lib/gmbal-api-only-MODULE.jar b/lib/gmbal-api-only-MODULE.jar
new file mode 100644 (file)
index 0000000..06a5fcc
Binary files /dev/null and b/lib/gmbal-api-only-MODULE.jar differ
diff --git a/lib/groovy-2.5.6.jar b/lib/groovy-2.5.6.jar
new file mode 100644 (file)
index 0000000..3a4edfa
Binary files /dev/null and b/lib/groovy-2.5.6.jar differ
diff --git a/lib/groovy-console-2.5.6.jar b/lib/groovy-console-2.5.6.jar
new file mode 100644 (file)
index 0000000..e18e426
Binary files /dev/null and b/lib/groovy-console-2.5.6.jar differ
diff --git a/lib/intervalstore-src-v0.4.jar b/lib/intervalstore-src-v0.4.jar
deleted file mode 100644 (file)
index 3feafbb..0000000
Binary files a/lib/intervalstore-src-v0.4.jar and /dev/null differ
diff --git a/lib/intervalstore-v1.0.jar b/lib/intervalstore-v1.0.jar
new file mode 100644 (file)
index 0000000..4f6101c
Binary files /dev/null and b/lib/intervalstore-v1.0.jar differ
diff --git a/lib/jaxws-rt-java9.jar b/lib/jaxws-rt-java9.jar
new file mode 100644 (file)
index 0000000..f380d18
Binary files /dev/null and b/lib/jaxws-rt-java9.jar differ
diff --git a/lib/mail-MODULE.jar b/lib/mail-MODULE.jar
new file mode 100644 (file)
index 0000000..367ca9e
Binary files /dev/null and b/lib/mail-MODULE.jar differ
diff --git a/lib/saaj-impl.jar b/lib/saaj-impl.jar
new file mode 100644 (file)
index 0000000..7eda25c
Binary files /dev/null and b/lib/saaj-impl.jar differ
diff --git a/modules b/modules
new file mode 100644 (file)
index 0000000..183a26f
--- /dev/null
+++ b/modules
@@ -0,0 +1 @@
+com.sun.istack.runtime,com.sun.xml.bind,com.sun.xml.fastinfoset,com.sun.xml.streambuffer,com.sun.xml.txw2,com.sun.xml.ws.policy,java.activation,java.annotation,java.base,java.compiler,java.datatransfer,java.desktop,java.logging,java.management,java.management.rmi,java.naming,java.prefs,java.rmi,java.scripting,java.security.sasl,java.sql,java.xml,java.xml.bind,java.xml.soap,java.xml.ws,javax.jws,jdk.httpserver,jdk.jsobject,jdk.unsupported,jdk.xml.dom,org.jvnet.mimepull,org.jvnet.staxex,javax.servlet.api,java.ws.rs
diff --git a/resources/images/jalview_logo_background_fade-640x480.png b/resources/images/jalview_logo_background_fade-640x480.png
new file mode 100644 (file)
index 0000000..4b65200
Binary files /dev/null and b/resources/images/jalview_logo_background_fade-640x480.png differ
diff --git a/resources/images/jalview_logo_background_getdown-640x480.png b/resources/images/jalview_logo_background_getdown-640x480.png
new file mode 100644 (file)
index 0000000..20d972a
Binary files /dev/null and b/resources/images/jalview_logo_background_getdown-640x480.png differ
diff --git a/resources/images/jalview_logo_background_getdown-progress-TEST2.png b/resources/images/jalview_logo_background_getdown-progress-TEST2.png
new file mode 100755 (executable)
index 0000000..1a9bf34
Binary files /dev/null and b/resources/images/jalview_logo_background_getdown-progress-TEST2.png differ
diff --git a/resources/images/jalview_logo_background_getdown-progress.png b/resources/images/jalview_logo_background_getdown-progress.png
new file mode 100644 (file)
index 0000000..fb87750
Binary files /dev/null and b/resources/images/jalview_logo_background_getdown-progress.png differ
diff --git a/resources/images/jalview_logo_background_getdown-progress1.png b/resources/images/jalview_logo_background_getdown-progress1.png
new file mode 100644 (file)
index 0000000..804063a
Binary files /dev/null and b/resources/images/jalview_logo_background_getdown-progress1.png differ
diff --git a/resources/images/jalview_logo_background_getdown-progress2.png b/resources/images/jalview_logo_background_getdown-progress2.png
new file mode 100644 (file)
index 0000000..fb87750
Binary files /dev/null and b/resources/images/jalview_logo_background_getdown-progress2.png differ
diff --git a/resources/images/jalview_logos.icns b/resources/images/jalview_logos.icns
new file mode 100755 (executable)
index 0000000..6c2ee9a
Binary files /dev/null and b/resources/images/jalview_logos.icns differ
diff --git a/resources/images/jalview_logos.ico b/resources/images/jalview_logos.ico
new file mode 100644 (file)
index 0000000..ba2ca82
Binary files /dev/null and b/resources/images/jalview_logos.ico differ
diff --git a/resources/images/jetset_jalview_splash.gif b/resources/images/jetset_jalview_splash.gif
new file mode 100644 (file)
index 0000000..cc144b1
Binary files /dev/null and b/resources/images/jetset_jalview_splash.gif differ
diff --git a/resources/images/jetset_jalview_splash.png b/resources/images/jetset_jalview_splash.png
new file mode 100644 (file)
index 0000000..3ce1f46
Binary files /dev/null and b/resources/images/jetset_jalview_splash.png differ
index 6b56f07..04e8982 100644 (file)
@@ -32,6 +32,7 @@ action.load_project = Load Project
 action.save_project = Save Project
 action.save_project_as = Save Project as...
 action.quit = Quit
+label.quit_jalview = Quit Jalview?
 action.expand_views = Expand Views
 action.gather_views = Gather Views
 action.page_setup = Page Setup...
@@ -184,22 +185,22 @@ label.out_to_textbox = Output to Textbox
 label.occupancy = Occupancy
 # delete Clustal - use FileFormat name instead
 label.clustal = Clustal
-# label.colourScheme_<schemeName> as in JalviewColourScheme
+# label.colourScheme_<schemeName> as in JalviewColourScheme, spaces removed
 label.colourScheme_clustal = Clustalx
 label.colourScheme_blosum62 = BLOSUM62 Score
-label.colourScheme_%_identity = Percentage Identity
+label.colourScheme_%identity = Percentage Identity
 label.colourScheme_zappo = Zappo
 label.colourScheme_taylor = Taylor
 label.colourScheme_hydrophobic = Hydrophobicity
-label.colourScheme_helix_propensity = Helix Propensity
-label.colourScheme_strand_propensity = Strand Propensity
-label.colourScheme_turn_propensity = Turn Propensity
-label.colourScheme_buried_index = Buried Index
+label.colourScheme_helixpropensity = Helix Propensity
+label.colourScheme_strandpropensity = Strand Propensity
+label.colourScheme_turnpropensity = Turn Propensity
+label.colourScheme_buriedindex = Buried Index
 label.colourScheme_purine/pyrimidine = Purine/Pyrimidine
 label.colourScheme_nucleotide = Nucleotide
-label.colourScheme_t-coffee_scores = T-Coffee Scores
-label.colourScheme_rna_helices = By RNA Helices
-label.colourScheme_sequence_id = Sequence ID Colour
+label.colourScheme_t-coffeescores = T-Coffee Scores
+label.colourScheme_rnahelices = By RNA Helices
+label.colourScheme_sequenceid = Sequence ID Colour
 label.blc = BLC
 label.fasta = Fasta
 label.msf = MSF
@@ -997,7 +998,8 @@ label.toggled = Toggled
 label.marked = Marked
 label.containing = containing
 label.not_containing = not containing
-label.no_feature_of_type_found = No features of type {0} found.
+label.no_feature_of_type_found = No features of type {0} found
+label.no_feature_found_selection = No features of type {0} found in selection
 label.submission_params = Submission {0}
 label.empty_alignment_job = Empty Alignment Job
 label.add_new_sbrs_service = Add a new Simple Bioinformatics Rest Service
@@ -1312,6 +1314,7 @@ label.numeric_required = The value should be numeric
 label.filter = Filter
 label.filters = Filters
 label.join_conditions = Join conditions with
+label.delete_condition = Delete this condition
 label.score = Score
 label.colour_by_label = Colour by label
 label.variable_colour = Variable colour...
index c367eea..9efeca1 100644 (file)
@@ -32,6 +32,7 @@ action.load_project = Cargar proyecto
 action.save_project = Guardar proyecto
 action.save_project_as = Guardar proyecto como...
 action.quit = Salir
+label.quit_jalview = Salir Javliew?
 action.expand_views = Expandir vistas
 action.gather_views = Capturar vistas
 action.page_setup = Configuración de la página
@@ -183,19 +184,19 @@ label.clustal = Clustal
 # label.colourScheme_<schemeName> as in JalviewColourScheme
 label.colourScheme_clustal = Clustalx
 label.colourScheme_blosum62 = Puntuación del BLOSUM62
-label.colourScheme_%_identity = Porcentaje de identidad
+label.colourScheme_%identity = Porcentaje de identidad
 label.colourScheme_zappo = Zappo
 label.colourScheme_taylor = Taylor
 label.colourScheme_hydrophobic = Hidrofobicidad
-label.colourScheme_helix_propensity = Tendencia de la hélice
-label.colourScheme_strand_propensity = Tendencia de la hebra
-label.colourScheme_turn_propensity = Tendencia de giro
-label.colourScheme_buried_index = Índice de encubrimiento
+label.colourScheme_helixpropensity = Tendencia de la hélice
+label.colourScheme_strandpropensity = Tendencia de la hebra
+label.colourScheme_turnpropensity = Tendencia de giro
+label.colourScheme_buriedindex = Índice de encubrimiento
 label.colourScheme_purine/pyrimidine = Purina/Pirimidina
 label.colourScheme_nucleotide = Nucleótido
-label.colourScheme_t-coffee_scores = Puntuación del T-Coffee
-label.colourScheme_rna_helices = Por hélices de RNA
-label.colourScheme_sequence_id = Color de ID de secuencia
+label.colourScheme_t-coffeescores = Puntuación del T-Coffee
+label.colourScheme_rnahelices = Por hélices de RNA
+label.colourScheme_sequenceid = Color de ID de secuencia
 label.blc = BLC
 label.fasta = Fasta
 label.msf = MSF
@@ -922,7 +923,8 @@ label.toggled = Invertida
 label.marked = Marcada
 label.containing = conteniendo
 label.not_containing = no conteniendo
-label.no_feature_of_type_found = No se han encontrado características del tipo {0}.
+label.no_feature_of_type_found = No se han encontrado características del tipo {0}
+label.no_feature_found_selection = No se han encontrado características del tipo {0} en la región seleccionada
 label.submission_params = Envío {0}
 label.empty_alignment_job = Trabajo de alineamiento vacío
 label.add_new_sbrs_service = Añadir un nuevo SBRS
@@ -1312,6 +1314,7 @@ label.matchCondition_ge = >=
 label.numeric_required = Valor numérico requerido
 label.filter = Filtro
 label.filters = Filtros
+label.delete_condition = Borrar esta condición
 label.join_conditions = Combinar condiciones con
 label.score = Puntuación
 label.colour_by_label = Colorear por texto
index b15c3cc..c6553f8 100644 (file)
@@ -37,8 +37,7 @@ import jalview.util.MessageManager;
 
 import java.awt.Color;
 import java.awt.Dimension;
-import java.awt.Event;
-import java.awt.Font;
+import java.awt.event.InputEvent;import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Image;
 // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug
@@ -893,7 +892,7 @@ public class AppletPDBCanvas extends Panel
     MCMatrix objmat = new MCMatrix(3, 3);
     objmat.setIdentity();
 
-    if ((evt.getModifiers() & Event.META_MASK) != 0)
+    if ((evt.getModifiersEx() & InputEvent.META_DOWN_MASK) != 0)
     {
       objmat.rotatez(((mx - omx)));
     }
index 904e307..a2ce2ae 100755 (executable)
@@ -83,14 +83,14 @@ public class Atom
     resNumber = Integer.parseInt(str.substring(22, 26).trim());
     resNumIns = str.substring(22, 27).trim();
     insCode = str.substring(26, 27).charAt(0);
-    this.x = (new Float(str.substring(30, 38).trim()).floatValue());
-    this.y = (new Float(str.substring(38, 46).trim()).floatValue());
-    this.z = (new Float(str.substring(47, 55).trim()).floatValue());
+    this.x = (Float.valueOf(str.substring(30, 38).trim()).floatValue());
+    this.y = (Float.valueOf(str.substring(38, 46).trim()).floatValue());
+    this.z = (Float.valueOf(str.substring(47, 55).trim()).floatValue());
     // optional entries - see JAL-730
     String tm = str.substring(54, 60).trim();
     if (tm.length() > 0)
     {
-      occupancy = (new Float(tm)).floatValue();
+      occupancy = (Float.valueOf(tm)).floatValue();
     }
     else
     {
@@ -100,7 +100,7 @@ public class Atom
     tm = str.substring(60, 66).trim();
     if (tm.length() > 0)
     {
-      tfactor = (new Float(tm).floatValue());
+      tfactor = (Float.valueOf(tm).floatValue());
     }
     else
     {
index ab172f2..a34e574 100644 (file)
@@ -36,8 +36,7 @@ import jalview.structure.StructureSelectionManager;
 
 import java.awt.Color;
 import java.awt.Dimension;
-import java.awt.Event;
-import java.awt.Font;
+import java.awt.event.InputEvent;import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 // JBPNote TODO: This class is quite noisy - needs proper log.info/log.debug
@@ -858,7 +857,7 @@ public class PDBCanvas extends JPanel
     MCMatrix objmat = new MCMatrix(3, 3);
     objmat.setIdentity();
 
-    if ((evt.getModifiers() & Event.META_MASK) != 0)
+    if ((evt.getModifiersEx() & InputEvent.META_DOWN_MASK) != 0)
     {
       objmat.rotatez(((mx - omx)));
     }
index 50d799f..d5f62de 100755 (executable)
@@ -138,16 +138,16 @@ class Branch extends Pattern
       n = RegOpt.opt(n, ignoreCase, dontMinQ);
     }
     n.setParent(this);
-    set(new Character(o.c), n, ignoreCase, dontMinQ);
+    set(Character.valueOf(o.c), n, ignoreCase, dontMinQ);
     if (ignoreCase)
     {
       if (o.c != o.altc)
       {
-        set(new Character(o.altc), n, ignoreCase, dontMinQ);
+        set(Character.valueOf(o.altc), n, ignoreCase, dontMinQ);
       }
       if (o.c != o.altc2 && o.altc != o.altc2)
       {
-        set(new Character(o.altc2), n, ignoreCase, dontMinQ);
+        set(Character.valueOf(o.altc2), n, ignoreCase, dontMinQ);
       }
     }
   }
@@ -250,7 +250,7 @@ class Branch extends Pattern
     {
       return -1;
     }
-    Pattern n = (Pattern) h.get(new Character(pt.src.charAt(pos)));
+    Pattern n = (Pattern) h.get(Character.valueOf(pt.src.charAt(pos)));
     if (n == null)
     {
       return -1;
index c00ddad..6d07427 100755 (executable)
@@ -19,6 +19,7 @@ import com.stevesoft.pat.wrap.StringWrap;
 /** Matches a Unicode punctuation character. */
 class UnicodePunct extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isPunct(s.charAt(from)) ? to : -1;
@@ -28,6 +29,7 @@ class UnicodePunct extends UniValidator
 /** Matches a Unicode white space character. */
 class UnicodeWhite extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isWhite(s.charAt(from)) ? to : -1;
@@ -39,6 +41,7 @@ class UnicodeWhite extends UniValidator
  */
 class NUnicodePunct extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isPunct(s.charAt(from)) ? to : -1;
@@ -50,6 +53,7 @@ class NUnicodePunct extends UniValidator
  */
 class NUnicodeWhite extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isWhite(s.charAt(from)) ? to : -1;
@@ -59,6 +63,7 @@ class NUnicodeWhite extends UniValidator
 /** Matches a Unicode word character: an alphanumeric or underscore. */
 class UnicodeW extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     if (from >= s.length())
@@ -74,6 +79,7 @@ class UnicodeW extends UniValidator
 /** Matches a character that is not a Unicode alphanumeric or underscore. */
 class NUnicodeW extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     if (from >= s.length())
@@ -89,6 +95,7 @@ class NUnicodeW extends UniValidator
 /** Matches a Unicode decimal digit. */
 class UnicodeDigit extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isDecimalDigit(s.charAt(from)) ? to
@@ -99,6 +106,7 @@ class UnicodeDigit extends UniValidator
 /** Matches a character that is not a Unicode digit. */
 class NUnicodeDigit extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isDecimalDigit(s.charAt(from)) ? to
@@ -109,6 +117,7 @@ class NUnicodeDigit extends UniValidator
 /** Matches a Unicode math character. */
 class UnicodeMath extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isMath(s.charAt(from)) ? to : -1;
@@ -118,6 +127,7 @@ class UnicodeMath extends UniValidator
 /** Matches a non-math Unicode character. */
 class NUnicodeMath extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isMath(s.charAt(from)) ? to : -1;
@@ -127,6 +137,7 @@ class NUnicodeMath extends UniValidator
 /** Matches a Unicode currency symbol. */
 class UnicodeCurrency extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isCurrency(s.charAt(from)) ? to : -1;
@@ -136,6 +147,7 @@ class UnicodeCurrency extends UniValidator
 /** Matches a non-currency symbol Unicode character. */
 class NUnicodeCurrency extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isCurrency(s.charAt(from)) ? to : -1;
@@ -145,6 +157,7 @@ class NUnicodeCurrency extends UniValidator
 /** Matches a Unicode alphabetic character. */
 class UnicodeAlpha extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && Prop.isAlphabetic(s.charAt(from)) ? to : -1;
@@ -154,6 +167,7 @@ class UnicodeAlpha extends UniValidator
 /** Matches a non-alphabetic Unicode character. */
 class NUnicodeAlpha extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && !Prop.isAlphabetic(s.charAt(from)) ? to
@@ -164,6 +178,7 @@ class NUnicodeAlpha extends UniValidator
 /** Matches an upper case Unicode character. */
 class UnicodeUpper extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && isUpper(s.charAt(from)) ? to : -1;
@@ -178,6 +193,7 @@ class UnicodeUpper extends UniValidator
 /** Matches an upper case Unicode character. */
 class UnicodeLower extends UniValidator
 {
+  @Override
   public int validate(StringLike s, int from, int to)
   {
     return from < s.length() && isLower(s.charAt(from)) ? to : -1;
@@ -599,7 +615,7 @@ public class Regex extends RegRes implements FilenameFilter
   /** Essentially clones the Regex object */
   public Regex(Regex r)
   {
-    super((RegRes) r);
+    super(r);
     dontMatchInQuotes = r.dontMatchInQuotes;
     esc = r.esc;
     ignoreCase = r.ignoreCase;
@@ -674,6 +690,7 @@ public class Regex extends RegRes implements FilenameFilter
    * patterns are equal as well as the most recent match. If a Regex is compare
    * with a RegRes, only the result of the most recent match is compared.
    */
+  @Override
   public boolean equals(Object o)
   {
     if (o instanceof Regex)
@@ -694,6 +711,7 @@ public class Regex extends RegRes implements FilenameFilter
   }
 
   /** A clone by any other name would smell as sweet. */
+  @Override
   public Object clone()
   {
     return new Regex(this);
@@ -1077,13 +1095,16 @@ public class Regex extends RegRes implements FilenameFilter
   {
     try
     {
-      return (Regex) getClass().newInstance();
+      return getClass().getDeclaredConstructor().newInstance();
     } catch (InstantiationException ie)
     {
       return null;
     } catch (IllegalAccessException iae)
     {
       return null;
+    } catch (ReflectiveOperationException roe)
+    {
+      return null;
     }
   }
 
@@ -1641,7 +1662,7 @@ public class Regex extends RegRes implements FilenameFilter
   {
     if (p instanceof Any && p.next == null)
     {
-      return (Pattern) new DotMulti(lo, hi);
+      return new DotMulti(lo, hi);
     }
     return RegOpt.safe4fm(p) ? (Pattern) new FastMulti(lo, hi, p)
             : (Pattern) new Multi(lo, hi, p);
@@ -1801,6 +1822,7 @@ public class Regex extends RegRes implements FilenameFilter
    * representations. Also be prepared to see some strange output if your
    * characters are not printable.
    */
+  @Override
   public String toString()
   {
     if (false && thePattern == null)
@@ -1901,6 +1923,7 @@ public class Regex extends RegRes implements FilenameFilter
    * 
    * @see com.stevesoft.pat.FileRegex
    */
+  @Override
   public boolean accept(File dir, String s)
   {
     return search(s);
index 837821e..a0b42ce 100755 (executable)
@@ -233,6 +233,7 @@ public class RegexReader extends Reader
    * 
    * @deprecated
    */
+  @Deprecated
   public int getMaxLines()
   {
     return max_lines;
@@ -243,6 +244,7 @@ public class RegexReader extends Reader
    * 
    * @deprecated
    */
+  @Deprecated
   public void setMaxLines(int ml)
   {
     max_lines = ml;
@@ -255,6 +257,7 @@ public class RegexReader extends Reader
    * 
    * @deprecated
    */
+  @Deprecated
   public char getEOLchar()
   {
     return EOLchar;
@@ -265,6 +268,7 @@ public class RegexReader extends Reader
    * 
    * @deprecated
    */
+  @Deprecated
   public void setEOLchar(char c)
   {
     EOLchar = c;
index 31fa2ba..c99bfea 100755 (executable)
@@ -43,13 +43,13 @@ public class RegexTokenizer implements Enumeration
     if (r.searchFrom(toParse, pos))
     {
       v.addElement(r.left().substring(pos));
-      vi.addElement(new Integer(r.matchFrom() + r.charsMatched()));
+      vi.addElement(Integer.valueOf(r.matchFrom() + r.charsMatched()));
       for (int i = 0; i < r.numSubs(); i++)
       {
         if (r.substring() != null)
         {
           v.addElement(r.substring(i + offset));
-          vi.addElement(new Integer(r.matchFrom(i + offset)
+          vi.addElement(Integer.valueOf(r.matchFrom(i + offset)
                   + r.charsMatched(i + offset)));
         }
       }
index 1d57a31..6b5e36c 100644 (file)
@@ -173,7 +173,7 @@ public abstract class ChimUtils
       float[] rgbValues = new float[4];
       for (int i = 0; i < rgbStrings.length; i++)
       {
-        Float f = new Float(rgbStrings[i]);
+        Float f = Float.valueOf(rgbStrings[i]);
         rgbValues[i] = f.floatValue();
       }
       if (rgbStrings.length == 4)
@@ -203,7 +203,7 @@ public abstract class ChimUtils
    */
   public static Integer makeModelKey(int model, int subModel)
   {
-    return new Integer(model * MAX_SUB_MODELS + subModel);
+    return Integer.valueOf(model * MAX_SUB_MODELS + subModel);
   }
 
   // invoked by the getResdiue (parseConnectivityReplies in
index 9a29a99..4f871d3 100644 (file)
@@ -200,7 +200,7 @@ public class ChimeraChain implements ChimeraStructuralObject
    */
   public ChimeraResidue getResidue(String index)
   {
-    // Integer index = new Integer(residueIndex);
+    // Integer index = Integer.valueOf(residueIndex);
     if (residueMap.containsKey(index))
       return residueMap.get(index);
     return null;
index 09a9713..22c9098 100644 (file)
@@ -121,8 +121,8 @@ public class StructureManager
       for (String chimObjName : names)
       {
         // get or open the corresponding models if they already exist
-        List<ChimeraModel> currentModels = chimeraManager.getChimeraModels(
-                chimObjName, type);
+        List<ChimeraModel> currentModels = chimeraManager
+                .getChimeraModels(chimObjName, type);
         if (currentModels.size() == 0)
         {
           // open and return models
@@ -562,11 +562,11 @@ public class StructureManager
         // Get the corresponding "real" model
         if (chimeraManager.hasChimeraModel(modelNumber, subModelNumber))
         {
-          ChimeraModel dataModel = chimeraManager.getChimeraModel(
-                  modelNumber, subModelNumber);
-          if (dataModel.getResidueCount() == selectedModel
-                  .getResidueCount()
-                  || dataModel.getModelType() == StructureManager.ModelType.SMILES)
+          ChimeraModel dataModel = chimeraManager
+                  .getChimeraModel(modelNumber, subModelNumber);
+          if (dataModel.getResidueCount() == selectedModel.getResidueCount()
+                  || dataModel
+                          .getModelType() == StructureManager.ModelType.SMILES)
           {
             // Select the entire model
             addChimSelection(dataModel);
@@ -576,8 +576,8 @@ public class StructureManager
           {
             for (ChimeraChain selectedChain : selectedModel.getChains())
             {
-              ChimeraChain dataChain = dataModel.getChain(selectedChain
-                      .getChainId());
+              ChimeraChain dataChain = dataModel
+                      .getChain(selectedChain.getChainId());
               if (selectedChain.getResidueCount() == dataChain
                       .getResidueCount())
               {
@@ -931,6 +931,7 @@ public class StructureManager
       pathList.add("/usr/local/chimera/bin/chimera");
       pathList.add("/usr/local/bin/chimera");
       pathList.add("/usr/bin/chimera");
+      pathList.add(System.getProperty("user.home") + "/opt/bin/chimera");
     }
     else if (os.startsWith("Windows"))
     {
index b5cefe0..8473904 100755 (executable)
@@ -586,7 +586,7 @@ public class AlignmentSorter
 
     for (int i = 0; i < alignment.length; i++)
     {
-      ids[i] = (new Float(alignment[i].getName().substring(8)))
+      ids[i] = (Float.valueOf(alignment[i].getName().substring(8)))
               .floatValue();
     }
 
index 0af5d20..c3de19b 100755 (executable)
@@ -695,7 +695,7 @@ public class Conservation
 
       max = Math.max(max, bigtot);
 
-      quality.addElement(new Double(bigtot));
+      quality.addElement(Double.valueOf(bigtot));
     }
 
     double newmax = -Double.MAX_VALUE;
@@ -707,7 +707,7 @@ public class Conservation
       tmp = ((max - tmp) * (size - cons2GapCounts[j])) / size;
 
       // System.out.println(tmp+ " " + j);
-      quality.setElementAt(new Double(tmp), j);
+      quality.setElementAt(Double.valueOf(tmp), j);
 
       if (tmp > newmax)
       {
index d07253e..137b7f8 100644 (file)
@@ -65,7 +65,7 @@ public final class GeneticCodes
       loadAmbiguityCodes(AMBIGUITY_CODES_FILE);
       loadCodes(RESOURCE_FILE);
     }
-  };
+  }
 
   /**
    * Returns the singleton instance of this class
@@ -117,6 +117,11 @@ public final class GeneticCodes
     try
     {
       InputStream is = getClass().getResourceAsStream(fileName);
+      if (is == null)
+      {
+        System.err.println("Resource file not found: " + fileName);
+        return;
+      }
       BufferedReader dataIn = new BufferedReader(new InputStreamReader(is));
 
       /*
@@ -136,9 +141,15 @@ public final class GeneticCodes
     } catch (IOException | NullPointerException e)
     {
       Cache.log.error(
-              "Error reading genetic codes data file: "
+              "Error reading genetic codes data file " + fileName + ": "
               + e.getMessage());
     }
+    if (codeTables.isEmpty())
+    {
+      System.err.println(
+              "No genetic code tables loaded, check format of file "
+                      + fileName);
+    }
   }
 
   /**
@@ -157,6 +168,11 @@ public final class GeneticCodes
     try
     {
       InputStream is = getClass().getResourceAsStream(fileName);
+      if (is == null)
+      {
+        System.err.println("Resource file not found: " + fileName);
+        return;
+      }
       BufferedReader dataIn = new BufferedReader(new InputStreamReader(is));
       String line = "";
       while (line != null)
@@ -165,8 +181,16 @@ public final class GeneticCodes
         if (line != null && !"DNA".equals(line.toUpperCase()))
         {
           String[] tokens = line.split("\\t");
+          if (tokens.length == 2)
+          {
           ambiguityCodes.put(tokens[0].toUpperCase(),
                   tokens[1].toUpperCase());
+          }
+          else
+          {
+            System.err.println(
+                    "Unexpected data in " + fileName + ": " + line);
+          }
         }
       }
     } catch (IOException e)
index 629a8a3..4b68d93 100644 (file)
@@ -134,7 +134,7 @@ public class ParseProperties
           double score = Double.NaN;
           try
           {
-            score = new Double(sstring).doubleValue();
+            score = Double.valueOf(sstring).doubleValue();
           } catch (Exception e)
           {
             // don't try very hard to parse if regex was wrong.
index fabd0c6..fdca89d 100755 (executable)
@@ -45,8 +45,8 @@ public class SeqsetUtils
   {
     Hashtable sqinfo = new Hashtable();
     sqinfo.put("Name", seq.getName());
-    sqinfo.put("Start", new Integer(seq.getStart()));
-    sqinfo.put("End", new Integer(seq.getEnd()));
+    sqinfo.put("Start", Integer.valueOf(seq.getStart()));
+    sqinfo.put("End", Integer.valueOf(seq.getEnd()));
     if (seq.getDescription() != null)
     {
       sqinfo.put("Description", seq.getDescription());
index b681aa6..1342bb2 100644 (file)
@@ -227,14 +227,14 @@ public class StructureFrequency
           maxResidue = "{";
         }
       }
-      residueHash.put(MAXCOUNT, new Integer(count));
+      residueHash.put(MAXCOUNT, Integer.valueOf(count));
       residueHash.put(MAXRESIDUE, maxResidue);
 
       percentage = ((float) count * 100) / jSize;
-      residueHash.put(PID_GAPS, new Float(percentage));
+      residueHash.put(PID_GAPS, Float.valueOf(percentage));
 
       percentage = ((float) count * 100) / nongap;
-      residueHash.put(PID_NOGAPS, new Float(percentage));
+      residueHash.put(PID_NOGAPS, Float.valueOf(percentage));
 
       if (result[i] == null)
       {
@@ -261,14 +261,14 @@ public class StructureFrequency
           residueHash.put(PAIRPROFILE, pairs);
         }
 
-        residueHash.put(MAXCOUNT, new Integer(count));
+        residueHash.put(MAXCOUNT, Integer.valueOf(count));
         residueHash.put(MAXRESIDUE, maxResidue);
 
         percentage = ((float) count * 100) / jSize;
-        residueHash.put(PID_GAPS, new Float(percentage));
+        residueHash.put(PID_GAPS, Float.valueOf(percentage));
 
         percentage = ((float) count * 100) / nongap;
-        residueHash.put(PID_NOGAPS, new Float(percentage));
+        residueHash.put(PID_NOGAPS, Float.valueOf(percentage));
 
         result[bpEnd] = residueHash;
       }
index e506be2..8545e94 100644 (file)
@@ -58,7 +58,7 @@ public class FeatureDistanceModel extends DistanceScoreModel
     FeatureDistanceModel instance;
     try
     {
-      instance = this.getClass().newInstance();
+      instance = this.getClass().getDeclaredConstructor().newInstance();
       instance.configureFromAlignmentView(view);
       return instance;
     } catch (InstantiationException | IllegalAccessException e)
@@ -66,6 +66,9 @@ public class FeatureDistanceModel extends DistanceScoreModel
       System.err.println("Error in " + getClass().getName()
               + ".getInstance(): " + e.getMessage());
       return null;
+    } catch (ReflectiveOperationException roe)
+    {
+      return null;
     }
   }
 
@@ -188,7 +191,7 @@ public class FeatureDistanceModel extends DistanceScoreModel
   protected Map<SeqCigar, Set<String>> findFeatureTypesAtColumn(
           SeqCigar[] seqs, int columnPosition)
   {
-    Map<SeqCigar, Set<String>> sfap = new HashMap<SeqCigar, Set<String>>();
+    Map<SeqCigar, Set<String>> sfap = new HashMap<>();
     for (SeqCigar seq : seqs)
     {
       int spos = seq.findPosition(columnPosition);
@@ -197,7 +200,7 @@ public class FeatureDistanceModel extends DistanceScoreModel
         /*
          * position is not a gap
          */
-        Set<String> types = new HashSet<String>();
+        Set<String> types = new HashSet<>();
         List<SequenceFeature> sfs = fr.findFeaturesAtResidue(
                 seq.getRefSeq(), spos);
         for (SequenceFeature sf : sfs)
index 85fb03c..eb3be88 100644 (file)
@@ -106,6 +106,7 @@ import java.awt.event.KeyListener;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.net.URLEncoder;
 import java.util.Arrays;
@@ -1569,7 +1570,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     try
     {
       new URL(url);
-      url = URLEncoder.encode(url);
+      url = URLEncoder.encode(url, "UTF-8");
     }
     /*
      * When we finally deprecate 1.1 compatibility, we can start to use
@@ -1582,6 +1583,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       url = viewport.applet.getCodeBase() + url;
     }
+    catch (UnsupportedEncodingException ex)
+    {
+      System.err.println(
+              "WARNING = IMPLEMENTATION ERROR - UNSUPPORTED ENCODING EXCEPTION FOR "
+                      + url);
+      ex.printStackTrace();
+    }
     return url;
   }
 
index 055584a..45180db 100644 (file)
@@ -73,7 +73,7 @@ public class AlignViewport extends AlignmentViewport
       {
         try
         {
-          widthScale = new Float(param).floatValue();
+          widthScale = Float.valueOf(param).floatValue();
         } catch (Exception e)
         {
         }
@@ -96,7 +96,7 @@ public class AlignViewport extends AlignmentViewport
       {
         try
         {
-          heightScale = new Float(param).floatValue();
+          heightScale = Float.valueOf(param).floatValue();
         } catch (Exception e)
         {
         }
index 533226e..9456986 100644 (file)
@@ -350,7 +350,7 @@ public class AnnotationColourChooser extends Panel implements
     {
       try
       {
-        float f = new Float(thresholdValue.getText()).floatValue();
+        float f = Float.valueOf(thresholdValue.getText()).floatValue();
         slider.setValue((int) (f * 1000));
         adjustmentValueChanged(null);
       } catch (NumberFormatException ex)
index 1366f31..a0102b9 100755 (executable)
@@ -457,8 +457,8 @@ public class AnnotationLabels extends Panel
             .getAlignmentAnnotation();
 
     // DETECT RIGHT MOUSE BUTTON IN AWT
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
 
       PopupMenu popup = new PopupMenu(
index 50bc184..2b50c32 100755 (executable)
@@ -354,8 +354,8 @@ public class AnnotationPanel extends Panel
       }
     }
 
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK
             && activeRow != -1)
     {
       if (av.getColumnSelection() == null
index 53782c0..7b489ea 100644 (file)
@@ -49,7 +49,8 @@ import java.util.Map;
  * @author Jim Procter and Andrew Waterhouse
  * 
  */
-public class EmbmenuFrame extends Frame implements MouseListener
+public class EmbmenuFrame extends Frame
+        implements MouseListener, AutoCloseable
 {
   protected static final Font FONT_ARIAL_PLAIN_11 = new Font("Arial",
           Font.PLAIN, 11);
@@ -59,7 +60,7 @@ public class EmbmenuFrame extends Frame implements MouseListener
   /**
    * map from labels to popup menus for the embedded menubar
    */
-  protected Map<Label, PopupMenu> embeddedPopup = new HashMap<Label, PopupMenu>();
+  protected Map<Label, PopupMenu> embeddedPopup = new HashMap<>();
 
   /**
    * the embedded menu is built on this and should be added to the frame at the
@@ -199,6 +200,7 @@ public class EmbmenuFrame extends Frame implements MouseListener
     return embeddedMenu;
   }
 
+  @Override
   public void mousePressed(MouseEvent evt)
   {
     PopupMenu popup = null;
@@ -223,18 +225,22 @@ public class EmbmenuFrame extends Frame implements MouseListener
     return embeddedPopup.get(source);
   }
 
+  @Override
   public void mouseClicked(MouseEvent evt)
   {
   }
 
+  @Override
   public void mouseReleased(MouseEvent evt)
   {
   }
 
+  @Override
   public void mouseEntered(MouseEvent evt)
   {
   }
 
+  @Override
   public void mouseExited(MouseEvent evt)
   {
   }
@@ -262,11 +268,13 @@ public class EmbmenuFrame extends Frame implements MouseListener
   /**
    * calls destroyMenus()
    */
-  public void finalize() throws Throwable
+  @Override
+  public void close()
   {
     destroyMenus();
     embeddedPopup = null;
     embeddedMenu = null;
-    super.finalize();
+    // no close for Frame
+    // super.finalize();
   }
 }
index 5569ab0..0d70660 100644 (file)
@@ -311,7 +311,7 @@ public class FeatureColourChooser extends Panel implements ActionListener,
   {
     try
     {
-      float f = new Float(thresholdValue.getText()).floatValue();
+      float f = Float.valueOf(thresholdValue.getText()).floatValue();
       slider.setValue((int) (f * SCALE_FACTOR_1K));
       adjustmentValueChanged(null);
 
index a60aacd..489cbb1 100755 (executable)
@@ -703,7 +703,7 @@ public class FeatureSettings extends Panel
   public void mouseClicked(MouseEvent evt)
   {
     MyCheckbox check = (MyCheckbox) evt.getSource();
-    if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
+    if ((evt.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)
     {
       this.popupSort(check, fr.getMinMax(), evt.getX(), evt.getY());
     }
index 443ebce..8b74e32 100644 (file)
@@ -31,9 +31,9 @@ import java.awt.FlowLayout;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Frame;
+import java.awt.GraphicsEnvironment;
 import java.awt.Label;
 import java.awt.Panel;
-import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
@@ -144,7 +144,9 @@ public class FontChooser extends Panel implements ItemListener
    */
   void init()
   {
-    String fonts[] = Toolkit.getDefaultToolkit().getFontList();
+    // String fonts[] = Toolkit.getDefaultToolkit().getFontList();
+    String fonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment()
+            .getAvailableFontFamilyNames();
     for (int i = 0; i < fonts.length; i++)
     {
       fontName.addItem(fonts[i]);
index af1c47b..1d37d08 100755 (executable)
@@ -279,8 +279,8 @@ public class IdPanel extends Panel
 
     int seq = alignPanel.seqPanel.findSeq(e);
 
-    if ((e.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((e.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       SequenceI sq = av.getAlignment().getSequenceAt(seq);
 
index 96138bf..8f72260 100755 (executable)
@@ -114,8 +114,8 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mouseClicked(MouseEvent evt)
   {
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       showPopupMenu(evt);
     }
@@ -140,8 +140,8 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mousePressed(MouseEvent evt)
   {
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       if (!Platform.isAMac())
       {
@@ -185,8 +185,8 @@ public class OverviewPanel extends Panel implements Runnable,
   @Override
   public void mouseDragged(MouseEvent evt)
   {
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       if (!Platform.isAMac())
       {
index c91449f..d3f4a69 100755 (executable)
@@ -96,8 +96,8 @@ public class ScalePanel extends Panel
 
     min = res;
     max = res;
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       rightMouseButtonPressed(evt, res);
     }
index e07dae6..fee68c8 100644 (file)
@@ -499,8 +499,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
     // For now, ignore the mouseWheel font resizing on Macs
     // As the Button2_mask always seems to be true
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON2_DOWN_MASK) == InputEvent.BUTTON2_DOWN_MASK
             && !av.MAC)
     {
       mouseWheelPressed = true;
@@ -1432,8 +1432,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
 
     // DETECT RIGHT MOUSE BUTTON IN AWT
-    if ((evt.getModifiers()
-            & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+    if ((evt.getModifiersEx()
+            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
     {
       List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
               sequence.findPosition(column + 1));
index b5e3342..671fee1 100644 (file)
@@ -46,7 +46,7 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 
 public class TreePanel extends EmbmenuFrame
-        implements ActionListener, ItemListener
+        implements ActionListener, ItemListener, AutoCloseable
 {
   SequenceI[] seq;
 
@@ -72,11 +72,11 @@ public class TreePanel extends EmbmenuFrame
   }
 
   @Override
-  public void finalize() throws Throwable
+  public void close()
   {
     ap = null;
     av = null;
-    super.finalize();
+    super.close();
   }
 
   /**
index cc41c53..c66e63e 100755 (executable)
@@ -64,6 +64,8 @@ import java.util.Vector;
 import javax.swing.LookAndFeel;
 import javax.swing.UIManager;
 
+import com.threerings.getdown.util.LaunchUtil;
+
 import groovy.lang.Binding;
 import groovy.util.GroovyScriptEngine;
 
@@ -105,7 +107,7 @@ public class Jalview
         perms.add(new AllPermission());
         return (perms);
       }
-
+    
       @Override
       public void refresh()
       {
@@ -201,6 +203,23 @@ public class Jalview
             + System.getProperty("os.name") + " "
             + System.getProperty("os.version"));
 
+    String appdirString = System.getProperty("getdownappdir");
+    if (appdirString != null && appdirString.length() > 0)
+    {
+      final File appdir = new File(appdirString);
+      new Thread()
+      {
+        @Override
+        public void run()
+        {
+          LaunchUtil.upgradeGetdown(
+                  new File(appdir, "getdown-launcher-old.jar"),
+                  new File(appdir, "getdown-launcher.jar"),
+                  new File(appdir, "getdown-launcher-new.jar"));
+        }
+      }.start();
+
+    }
     ArgsParser aparser = new ArgsParser(args);
     boolean headless = false;
 
@@ -335,6 +354,18 @@ public class Jalview
     {
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
+
+      try
+      {
+        JalviewTaskbar.setTaskbar(this);
+      } catch (Exception e)
+      {
+        e.printStackTrace();
+      } catch (Throwable t)
+      {
+        t.printStackTrace();
+      }
+
       desktop.setVisible(true);
       desktop.startServiceDiscovery();
       if (!aparser.contains("nousagestats"))
@@ -963,7 +994,7 @@ public class Jalview
     }
     try
     {
-      Map<String, Object> vbinding = new HashMap<>();
+      Map<String, java.lang.Object> vbinding = new HashMap<>();
       vbinding.put("Jalview", this);
       if (af != null)
       {
index a60496c..8873cc9 100644 (file)
@@ -198,7 +198,7 @@ public class JalviewLite extends Applet
       int apos = -1;
       try
       {
-        apos = new Integer(position).intValue();
+        apos = Integer.valueOf(position).intValue();
         apos--;
       } catch (NumberFormatException ex)
       {
@@ -337,7 +337,7 @@ public class JalviewLite extends Applet
           int from = -1, to = -1;
           try
           {
-            from = new Integer(cl.substring(0, p)).intValue();
+            from = Integer.valueOf(cl.substring(0, p)).intValue();
             from--;
           } catch (NumberFormatException ex)
           {
@@ -348,7 +348,7 @@ public class JalviewLite extends Applet
           }
           try
           {
-            to = new Integer(cl.substring(p + 1)).intValue();
+            to = Integer.valueOf(cl.substring(p + 1)).intValue();
             to--;
           } catch (NumberFormatException ex)
           {
@@ -408,7 +408,7 @@ public class JalviewLite extends Applet
           int r = -1;
           try
           {
-            r = new Integer(cl).intValue();
+            r = Integer.valueOf(cl).intValue();
             r--;
           } catch (NumberFormatException ex)
           {
@@ -1176,7 +1176,7 @@ public class JalviewLite extends Applet
         try
         {
           StructureSelectionManager.getStructureSelectionManager(me)
-                  .mouseOverStructure(new Integer(pdbResNum).intValue(),
+                  .mouseOverStructure(Integer.valueOf(pdbResNum).intValue(),
                           chain, pdbfile);
           if (debug)
           {
@@ -1212,8 +1212,8 @@ public class JalviewLite extends Applet
       {
         try
         {
-          alf.scrollTo(new Integer(topRow).intValue(),
-                  new Integer(leftHandColumn).intValue());
+          alf.scrollTo(Integer.valueOf(topRow).intValue(),
+                  Integer.valueOf(leftHandColumn).intValue());
 
         } catch (Exception ex)
         {
@@ -1244,7 +1244,7 @@ public class JalviewLite extends Applet
       {
         try
         {
-          alf.scrollToRow(new Integer(topRow).intValue());
+          alf.scrollToRow(Integer.valueOf(topRow).intValue());
 
         } catch (Exception ex)
         {
@@ -1276,7 +1276,7 @@ public class JalviewLite extends Applet
       {
         try
         {
-          alf.scrollToColumn(new Integer(leftHandColumn).intValue());
+          alf.scrollToColumn(Integer.valueOf(leftHandColumn).intValue());
 
         } catch (Exception ex)
         {
diff --git a/src/jalview/bin/JalviewTaskbar.java b/src/jalview/bin/JalviewTaskbar.java
new file mode 100644 (file)
index 0000000..5747263
--- /dev/null
@@ -0,0 +1,39 @@
+package jalview.bin;
+
+import java.awt.Image;
+import java.awt.Taskbar;
+
+public class JalviewTaskbar
+{
+  public JalviewTaskbar()
+  {
+  }
+
+  protected static void setTaskbar(Jalview jalview)
+  {
+    
+    if (Taskbar.isTaskbarSupported())
+    {
+      Taskbar tb = Taskbar.getTaskbar();
+      if (tb.isSupported(Taskbar.Feature.ICON_IMAGE))
+      {
+        try
+        {
+          java.net.URL url = jalview.getClass()
+                  .getResource("/images/JalviewLogo_Huge.png");
+          if (url != null)
+          {
+            Image image = java.awt.Toolkit.getDefaultToolkit()
+                    .createImage(url);
+            tb.setIconImage(image);
+          }
+        } catch (Exception e)
+        {
+          e.printStackTrace();
+        }
+      }
+    }
+
+  }
+
+}
diff --git a/src/jalview/bin/Launcher.java b/src/jalview/bin/Launcher.java
new file mode 100644 (file)
index 0000000..aec3acd
--- /dev/null
@@ -0,0 +1,120 @@
+package jalview.bin;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.util.ArrayList;
+
+public class Launcher
+{
+
+  private final static String startClass = "jalview.bin.Jalview";
+
+  private final static int maxHeapSizePerCent = 95;
+
+  private final static String dockIconPath = "JalviewLogo_Huge.png";
+
+  public static void main(String[] args)
+  {
+    final String javaBin = System.getProperty("java.home") + File.separator
+            + "bin" + File.separator + "java";
+
+    ArrayList<String> command = new ArrayList<>();
+    command.add(javaBin);
+
+    boolean isAMac = System.getProperty("os.name").indexOf("Mac") > -1;
+
+    for (String jvmArg : ManagementFactory.getRuntimeMXBean()
+            .getInputArguments())
+    {
+      command.add(jvmArg);
+    }
+    command.add("-cp");
+    command.add(ManagementFactory.getRuntimeMXBean().getClassPath());
+    ArrayList<String> arguments = new ArrayList<>();
+    for (String arg : args)
+    {
+      arguments.add(arg);
+    }
+
+    // add memory setting if not specified
+    boolean memSet = false;
+    boolean dockIcon = false;
+    ARG: for (int i = 0; i < command.size(); i++)
+    {
+      String arg = command.get(i);
+      if (arg.startsWith("-Xmx"))
+      {
+        memSet = true;
+      }
+      else if (arg.startsWith("-Xdock:icon"))
+      {
+        dockIcon = true;
+      }
+    }
+
+    if (!memSet)
+    {
+      long maxMemLong = -1;
+      long physicalMem = getPhysicalMemory();
+      if (physicalMem > 0)
+      {
+        maxMemLong = physicalMem * maxHeapSizePerCent / 100;
+      }
+      if (maxMemLong > 0)
+      {
+        command.add("-Xmx" + Long.toString(maxMemLong));
+      }
+    }
+
+    if (!dockIcon && isAMac)
+    {
+      command.add("-Xdock:icon=" + dockIconPath);
+      // -Xdock:name=... doesn't actually work :(
+      // Leaving it in in case it gets fixed
+      command.add("-Xdock:name=" + "Jalview");
+    }
+
+    command.add(startClass);
+    command.addAll(arguments);
+
+    final ProcessBuilder builder = new ProcessBuilder(command);
+
+    System.out.println("COMMAND: " + String.join(" ", builder.command()));
+
+    try
+    {
+      builder.inheritIO();
+      Process process = builder.start();
+      process.waitFor();
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    // System.exit(0);
+
+  }
+
+  public static long getPhysicalMemory()
+  {
+    final OperatingSystemMXBean o = ManagementFactory
+            .getOperatingSystemMXBean();
+
+    try
+    {
+      if (o instanceof com.sun.management.OperatingSystemMXBean)
+      {
+        final com.sun.management.OperatingSystemMXBean osb = (com.sun.management.OperatingSystemMXBean) o;
+        return osb.getTotalPhysicalMemorySize();
+      }
+    } catch (NoClassDefFoundError e)
+    {
+      // com.sun.management.OperatingSystemMXBean doesn't exist in this JVM
+      System.out.println("No com.sun.management.OperatingSystemMXBean");
+    }
+
+    // We didn't get a com.sun.management.OperatingSystemMXBean.
+    return -1;
+  }
+
+}
index a82b98d..f45afa5 100644 (file)
@@ -166,9 +166,10 @@ public class AlignViewController implements AlignViewControllerI
     // JBPNote this routine could also mark rows, not just columns.
     // need a decent query structure to allow all types of feature searches
     BitSet bs = new BitSet();
-    SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
-            || extendCurrent) ? viewport.getAlignment()
-                    : viewport.getSelectionGroup();
+    boolean searchSelection = viewport.getSelectionGroup() != null
+            && !extendCurrent;
+    SequenceCollectionI sqcol = searchSelection ? viewport
+            .getSelectionGroup() : viewport.getAlignment();
 
     int nseq = findColumnsWithFeature(featureType, sqcol, bs);
 
@@ -204,9 +205,10 @@ public class AlignViewController implements AlignViewControllerI
     }
     else
     {
-      avcg.setStatus(MessageManager
-              .formatMessage("label.no_feature_of_type_found", new String[]
-              { featureType }));
+      String key = searchSelection ? "label.no_feature_found_selection"
+              : "label.no_feature_of_type_found";
+      avcg.setStatus(MessageManager.formatMessage(key,
+              new String[] { featureType }));
       if (!extendCurrent)
       {
         cs.clear();
index ac00fa2..3b0ca46 100755 (executable)
@@ -47,7 +47,7 @@ import java.util.Vector;
  * @author JimP
  * 
  */
-public class Alignment implements AlignmentI
+public class Alignment implements AlignmentI, AutoCloseable
 {
   private Alignment dataset;
 
@@ -302,15 +302,20 @@ public class Alignment implements AlignmentI
   }
 
   @Override
-  public void finalize() throws Throwable
+  public void close()
   {
     if (getDataset() != null)
     {
-      getDataset().removeAlignmentRef();
+      try
+      {
+        getDataset().removeAlignmentRef();
+      } catch (Throwable e)
+      {
+        e.printStackTrace();
+      }
     }
 
     nullReferences();
-    super.finalize();
   }
 
   /**
index ee9389c..2ee4503 100755 (executable)
@@ -991,7 +991,7 @@ public class AlignmentAnnotation
           seqPos = i + startRes;
         }
 
-        sequenceMapping.put(new Integer(seqPos), annotations[i]);
+        sequenceMapping.put(Integer.valueOf(seqPos), annotations[i]);
       }
     }
 
@@ -1030,7 +1030,7 @@ public class AlignmentAnnotation
     {
       for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
       {
-        index = new Integer(a);
+        index = Integer.valueOf(a);
         Annotation annot = sequenceMapping.get(index);
         if (annot != null)
         {
index c33abb3..cea9de7 100755 (executable)
@@ -169,7 +169,7 @@ public class BinarySequence extends Sequence
 
     for (int i = 0; i < binary.length; i++)
     {
-      out += (new Integer(binary[i])).toString();
+      out += (Integer.valueOf(binary[i])).toString();
 
       if (i < (binary.length - 1))
       {
index 3ccaab8..6f14e21 100644 (file)
@@ -106,7 +106,7 @@ public class ColumnSelection
     void remove(int col)
     {
 
-      Integer colInt = new Integer(col);
+      Integer colInt = Integer.valueOf(col);
 
       if (selected.get(col))
       {
@@ -204,7 +204,7 @@ public class ColumnSelection
           // clear shifted bits and update List of selected columns
           selected.clear(temp);
           mask.set(temp - change);
-          order.set(i, new Integer(temp - change));
+          order.set(i, Integer.valueOf(temp - change));
         }
       }
       // lastly update the bitfield all at once
@@ -309,7 +309,7 @@ public class ColumnSelection
     Integer colInt;
     for (int i = start; i < end; i++)
     {
-      colInt = new Integer(i);
+      colInt = Integer.valueOf(i);
       if (selection.contains(colInt))
       {
         selection.remove(colInt);
index f01bd4f..b1c9d86 100644 (file)
@@ -19,6 +19,18 @@ import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 
+/**
+ * A client for the Ensembl REST service /map endpoint, to convert from
+ * coordinates of one genome assembly to another.
+ * <p>
+ * Note that species and assembly identifiers passed to this class must be valid
+ * in Ensembl. They are not case sensitive.
+ * 
+ * @author gmcarstairs
+ * @see https://rest.ensembl.org/documentation/info/assembly_map
+ * @see https://rest.ensembl.org/info/assembly/human?content-type=text/xml
+ * @see https://rest.ensembl.org/info/species?content-type=text/xml
+ */
 public class EnsemblMap extends EnsemblRestClient
 {
   private static final String MAPPED = "mapped";
index e64c51a..617ab21 100644 (file)
@@ -64,9 +64,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
    * @see https://github.com/Ensembl/ensembl-rest/wiki/Change-log
    * @see http://rest.ensembl.org/info/rest?content-type=application/json
    */
-  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "9.0";
+  private static final String LATEST_ENSEMBLGENOMES_REST_VERSION = "10.0";
 
-  private static final String LATEST_ENSEMBL_REST_VERSION = "9.0";
+  private static final String LATEST_ENSEMBL_REST_VERSION = "10.0";
 
   private static final String REST_CHANGE_LOG = "https://github.com/Ensembl/ensembl-rest/wiki/Change-log";
 
index 8296985..7454eb6 100644 (file)
@@ -42,7 +42,8 @@ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl
   // domain properties default values:
   protected static final String DEFAULT_ENSEMBL_BASEURL = "https://rest.ensembl.org";
 
-  protected static final String DEFAULT_ENSEMBL_GENOMES_BASEURL = "https://rest.ensemblgenomes.org";
+  // ensemblgenomes REST service merged to ensembl 9th April 2019
+  protected static final String DEFAULT_ENSEMBL_GENOMES_BASEURL = DEFAULT_ENSEMBL_BASEURL;
 
   /*
    * accepts ENSG/T/E/P with 11 digits
@@ -102,10 +103,6 @@ abstract class EnsemblSequenceFetcher extends DbSourceProxyImpl
   public String getDbSource()
   {
     // NB ensure Uniprot xrefs are canonicalised from "Ensembl" to "ENSEMBL"
-    if (ensemblGenomesDomain.equals(getDomain()))
-    {
-      return DBRefSource.ENSEMBLGENOMES;
-    }
     return DBRefSource.ENSEMBL;
   }
 
index 14c057f..04525f0 100644 (file)
@@ -1,14 +1,14 @@
 package jalview.ext.htsjdk;
 
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+
 import htsjdk.samtools.util.CloseableIterator;
 import htsjdk.variant.variantcontext.VariantContext;
 import htsjdk.variant.vcf.VCFFileReader;
 import htsjdk.variant.vcf.VCFHeader;
 
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-
 /**
  * A thin wrapper for htsjdk classes to read either plain, or compressed, or
  * compressed and indexed VCF files
@@ -116,7 +116,7 @@ public class VCFReader implements Closeable, Iterable<VariantContext>
   {
     final CloseableIterator<VariantContext> it = reader.iterator();
     
-    return new CloseableIterator<VariantContext>()
+    return new CloseableIterator()
     {
       boolean atEnd = false;
 
@@ -145,7 +145,7 @@ public class VCFReader implements Closeable, Iterable<VariantContext>
           int vend = variant.getEnd();
           // todo what is the undeprecated way to get
           // the chromosome for the variant?
-          if (chrom.equals(variant.getChr()) && (vstart <= end)
+          if (chrom.equals(variant.getContig()) && (vstart <= end)
                   && (vend >= start))
           {
             return variant;
index a5b1110..c0a1e0d 100644 (file)
@@ -44,7 +44,6 @@ import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.io.File;
 import java.net.URL;
-import java.security.AccessControlException;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Hashtable;
@@ -614,74 +613,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    */
   private int _modelFileNameMap[];
 
-  // ////////////////////////////////
-  // /StructureListener
-  // @Override
-  public synchronized String[] getPdbFilex()
-  {
-    if (viewer == null)
-    {
-      return new String[0];
-    }
-    if (modelFileNames == null)
-    {
-      List<String> mset = new ArrayList<>();
-      _modelFileNameMap = new int[viewer.ms.mc];
-      String m = viewer.ms.getModelFileName(0);
-      if (m != null)
-      {
-        String filePath = m;
-        try
-        {
-          filePath = new File(m).getAbsolutePath();
-        } catch (AccessControlException x)
-        {
-          // usually not allowed to do this in applet
-          System.err.println(
-                  "jmolBinding: Using local file string from Jmol: " + m);
-        }
-        if (filePath.indexOf("/file:") != -1)
-        {
-          // applet path with docroot - discard as format won't match pdbfile
-          filePath = m;
-        }
-        mset.add(filePath);
-        _modelFileNameMap[0] = 0; // filename index for first model is always 0.
-      }
-      int j = 1;
-      for (int i = 1; i < viewer.ms.mc; i++)
-      {
-        m = viewer.ms.getModelFileName(i);
-        String filePath = m;
-        if (m != null)
-        {
-          try
-          {
-            filePath = new File(m).getAbsolutePath();
-          } catch (AccessControlException x)
-          {
-            // usually not allowed to do this in applet, so keep raw handle
-            // System.err.println("jmolBinding: Using local file string from
-            // Jmol: "+m);
-          }
-        }
-
-        /*
-         * add this model unless it is read from a structure file we have
-         * already seen (example: 2MJW is an NMR structure with 10 models)
-         */
-        if (!mset.contains(filePath))
-        {
-          mset.add(filePath);
-          _modelFileNameMap[j] = i; // record the model index for the filename
-          j++;
-        }
-      }
-      modelFileNames = mset.toArray(new String[mset.size()]);
-    }
-    return modelFileNames;
-  }
-
   @Override
   public synchronized String[] getStructureFiles()
   {
@@ -876,7 +807,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       try
       {
         // recover PDB filename for the model hovered over.
-        int mnumber = new Integer(mdlId).intValue() - 1;
+        int mnumber = Integer.valueOf(mdlId).intValue() - 1;
         if (_modelFileNameMap != null)
         {
           int _mp = _modelFileNameMap.length - 1;
index d50ad87..b8ba847 100644 (file)
@@ -137,13 +137,13 @@ public class Annotate3D
   public static Iterator<Reader> getRNAMLForPDBFileAsString(String pdbfile)
           throws Exception
   {
-    List<NameValuePair> vals = new ArrayList<NameValuePair>();
+    List<NameValuePair> vals = new ArrayList<>();
     vals.add(new BasicNameValuePair("tool", "rnaview"));
     vals.add(new BasicNameValuePair("data", pdbfile));
     vals.add(new BasicNameValuePair("output", "rnaml"));
     // return processJsonResponseFor(HttpClientUtils.doHttpUrlPost(twoDtoolsURL,
     // vals));
-    ArrayList<Reader> readers = new ArrayList<Reader>();
+    ArrayList<Reader> readers = new ArrayList<>();
     final BufferedReader postResponse = HttpClientUtils
             .doHttpUrlPost(twoDtoolsURL, vals, 0, 0);
     readers.add(postResponse);
@@ -158,74 +158,8 @@ public class Annotate3D
     try
     {
       final JSONArray responses = (JSONArray) jp.parse(respons);
-      final Iterator rvals = responses.iterator();
-      return new Iterator<Reader>()
-      {
-        @Override
-        public boolean hasNext()
-        {
-          return rvals.hasNext();
-        }
-
-        @Override
-        public Reader next()
-        {
-          JSONObject val = (JSONObject) rvals.next();
-
-          Object sval = null;
-          try
-          {
-            sval = val.get("2D");
-          } catch (Exception x)
-          {
-            x.printStackTrace();
-          }
-          ;
-          if (sval == null)
-          {
-            System.err.println(
-                    "DEVELOPER WARNING: Annotate3d didn't return a '2D' tag in its response. Consider checking output of server. Response was :"
-                            + val.toString());
-
-            sval = "";
-          }
-          return new StringReader((sval instanceof JSONObject)
-                  ? ((JSONObject) sval).toString()
-                  : sval.toString());
-
-        }
-
-        @Override
-        public void remove()
-        {
-          throw new Error(
-                  MessageManager.getString("error.not_implemented_remove"));
-
-        }
-
-        @Override
-        protected Object clone() throws CloneNotSupportedException
-        {
-          throw new CloneNotSupportedException(
-                  MessageManager.getString("error.not_implemented_clone"));
-        }
-
-        @Override
-        public boolean equals(Object obj)
-        {
-          return super.equals(obj);
-        }
-
-        @Override
-        protected void finalize() throws Throwable
-        {
-          while (rvals.hasNext())
-          {
-            rvals.next();
-          }
-          super.finalize();
-        }
-      };
+      final RvalsIterator rvals = new RvalsIterator(responses);
+      return rvals;
     } catch (Exception foo)
     {
       throw new Exception(MessageManager.getString(
@@ -238,7 +172,7 @@ public class Annotate3D
   public static Iterator<Reader> getRNAMLForPDBId(String pdbid)
           throws Exception
   {
-    List<NameValuePair> vals = new ArrayList<NameValuePair>();
+    List<NameValuePair> vals = new ArrayList<>();
     vals.add(new BasicNameValuePair("tool", "rnaview"));
     vals.add(new BasicNameValuePair("pdbid", pdbid));
     vals.add(new BasicNameValuePair("output", "rnaml"));
@@ -246,9 +180,83 @@ public class Annotate3D
             + pdbid + "&output=rnaml");
     // return processJsonResponseFor(new
     // InputStreamReader(geturl.openStream()));
-    ArrayList<Reader> readers = new ArrayList<Reader>();
+    ArrayList<Reader> readers = new ArrayList<>();
     readers.add(new InputStreamReader(geturl.openStream()));
     return readers.iterator();
   }
 
 }
+
+class RvalsIterator implements Iterator, AutoCloseable
+{
+  private Iterator rvals;
+
+  protected RvalsIterator(JSONArray responses)
+  {
+    this.rvals = responses.iterator();
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return rvals.hasNext();
+  }
+
+  @Override
+  public Reader next()
+  {
+    JSONObject val = (JSONObject) rvals.next();
+
+    Object sval = null;
+    try
+    {
+      sval = val.get("2D");
+    } catch (Exception x)
+    {
+      x.printStackTrace();
+    }
+    ;
+    if (sval == null)
+    {
+      System.err.println(
+              "DEVELOPER WARNING: Annotate3d didn't return a '2D' tag in its response. Consider checking output of server. Response was :"
+                      + val.toString());
+
+      sval = "";
+    }
+    return new StringReader(
+            (sval instanceof JSONObject) ? ((JSONObject) sval).toString()
+                    : sval.toString());
+
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new Error(
+            MessageManager.getString("error.not_implemented_remove"));
+
+  }
+
+  @Override
+  protected Object clone() throws CloneNotSupportedException
+  {
+    throw new CloneNotSupportedException(
+            MessageManager.getString("error.not_implemented_clone"));
+  }
+
+  @Override
+  public boolean equals(Object obj)
+  {
+    return super.equals(obj);
+  }
+
+  @Override
+  public void close()
+  {
+    while (rvals.hasNext())
+    {
+      rvals.next();
+    }
+  }
+}
diff --git a/src/jalview/gui/APQHandlers.java b/src/jalview/gui/APQHandlers.java
new file mode 100644 (file)
index 0000000..31f7a6d
--- /dev/null
@@ -0,0 +1,150 @@
+package jalview.gui;
+
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+
+import java.awt.Desktop;
+import java.awt.desktop.AboutEvent;
+import java.awt.desktop.AboutHandler;
+import java.awt.desktop.PreferencesEvent;
+import java.awt.desktop.PreferencesHandler;
+import java.awt.desktop.QuitEvent;
+import java.awt.desktop.QuitHandler;
+import java.awt.desktop.QuitResponse;
+import java.awt.desktop.QuitStrategy;
+
+import javax.swing.JOptionPane;
+
+public class APQHandlers
+{
+  private static boolean setAPQHandlers = false;
+
+  public APQHandlers() {
+  }
+
+  protected static boolean setAPQHandlers(jalview.gui.Desktop jalviewDesktop)
+  {
+    // flagging this test to avoid unnecessary reflection
+    if (!setAPQHandlers)
+    {
+      // see if the Quit, About and Preferences handlers are available
+      Class desktopClass = Desktop.class;
+      Desktop hdesktop = Desktop.getDesktop();
+
+      try
+      {
+        Float specversion = Float.parseFloat(
+                System.getProperty("java.specification.version"));
+
+        if (specversion >= 9)
+        {
+          if (Platform.isAMac())
+          {
+            if (desktopClass.getDeclaredMethod("setAboutHandler",
+                    new Class[]
+                    { AboutHandler.class }) != null)
+            {
+
+              hdesktop.setAboutHandler(new AboutHandler()
+              {
+                @Override
+                public void handleAbout(AboutEvent e)
+                {
+                  jalviewDesktop.aboutMenuItem_actionPerformed(null);
+                }
+              });
+
+            }
+
+            if (desktopClass.getDeclaredMethod("setPreferencesHandler",
+                    new Class[]
+                    { PreferencesHandler.class }) != null)
+            {
+
+              hdesktop.setPreferencesHandler(
+                      new PreferencesHandler()
+              {
+                        @Override
+                        public void handlePreferences(
+                                PreferencesEvent e)
+                        {
+                          jalviewDesktop.preferences_actionPerformed(null);
+                        }
+                      });
+
+            }
+
+            if (desktopClass.getDeclaredMethod("setQuitHandler",
+                    new Class[]
+                    { QuitHandler.class }) != null)
+            {
+
+              hdesktop.setQuitHandler(new QuitHandler()
+              {
+                @Override
+                public void handleQuitRequestWith(
+                        QuitEvent e, QuitResponse r)
+                {
+                  boolean confirmQuit = jalview.bin.Cache
+                          .getDefault(jalviewDesktop.CONFIRM_KEYBOARD_QUIT,
+                                  true);
+                  int n;
+                  if (confirmQuit)
+                  {
+                    n = JOptionPane.showConfirmDialog(null,
+                            MessageManager.getString("label.quit_jalview"),
+                            MessageManager.getString("action.quit"),
+                            JOptionPane.OK_CANCEL_OPTION,
+                            JOptionPane.PLAIN_MESSAGE, null);
+                  }
+                  else
+                  {
+                    n = JOptionPane.OK_OPTION;
+                  }
+                  if (n == JOptionPane.OK_OPTION)
+                  {
+                    System.out.println("Shortcut Quit confirmed by user");
+                    jalviewDesktop.quit();
+                    r.performQuit(); // probably won't reach this line, but just
+                                     // in
+                                     // case
+                  }
+                  else
+                  {
+                    r.cancelQuit();
+                    System.out.println("Shortcut Quit cancelled by user");
+                  }
+                }
+              });
+              hdesktop.setQuitStrategy(
+                      QuitStrategy.CLOSE_ALL_WINDOWS);
+
+            }
+          }
+          setAPQHandlers = true;
+        }
+        else
+        {
+          System.out.println(
+                  "Not going to try setting APQ Handlers as java.spec.version is "
+                          + specversion);
+        }
+
+      } catch (Exception e)
+      {
+        System.out.println(
+                "Exception when looking for About, Preferences, Quit Handlers");
+        e.printStackTrace();
+      } catch (Throwable t)
+      {
+        System.out.println(
+                "Throwable when looking for About, Preferences, Quit Handlers");
+        t.printStackTrace();
+      }
+
+    }
+    
+    return setAPQHandlers;
+  }
+
+}
index a587ac3..0ab7515 100644 (file)
@@ -974,7 +974,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void setStatus(String text)
   {
-    statusBar.setText(text);
+    statusBar.setText(text == null || text.isEmpty() ? " " : text);
   }
 
   /*
@@ -1203,7 +1203,23 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           PrintWriter out = new PrintWriter(
                   new FileWriter(backupfiles.getTempFilePath()));
 
+          // TESTING code here
+          boolean TESTING = true;
+          if (TESTING)
+          {
+            out.print("; TESTSTART\n");
+            int count = 20;
+            for (int i = 0; i < count; i++)
+            {
+              // Thread.sleep(1000);
+              out.println("; TEST: " + (count - 1 - i));
+            }
+          }
           out.print(output);
+          if (TESTING)
+          {
+            out.print("; TESTEND\n");
+          }
           out.close();
           this.setTitle(file);
           statusBar.setText(MessageManager.formatMessage(
@@ -2092,7 +2108,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                     newGraphGroups.add(q, null);
                   }
                   newGraphGroups.set(newann.graphGroup,
-                          new Integer(++fgroup));
+                          Integer.valueOf(++fgroup));
                 }
                 newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                         .intValue();
@@ -2139,7 +2155,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                     newGraphGroups.add(q, null);
                   }
                   newGraphGroups.set(newann.graphGroup,
-                          new Integer(++fgroup));
+                          Integer.valueOf(++fgroup));
                 }
                 newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                         .intValue();
@@ -3104,7 +3120,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleAbove_actionPerformed(ActionEvent e)
   {
     viewport.setScaleAboveWrapped(scaleAbove.isSelected());
-    // TODO: do we actually need to update overview for scale above change ?
+    alignPanel.updateLayout();
     alignPanel.paintAlignment(true, false);
   }
 
@@ -3118,6 +3134,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleLeft_actionPerformed(ActionEvent e)
   {
     viewport.setScaleLeftWrapped(scaleLeft.isSelected());
+    alignPanel.updateLayout();
     alignPanel.paintAlignment(true, false);
   }
 
@@ -3131,6 +3148,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   protected void scaleRight_actionPerformed(ActionEvent e)
   {
     viewport.setScaleRightWrapped(scaleRight.isSelected());
+    alignPanel.updateLayout();
     alignPanel.paintAlignment(true, false);
   }
 
@@ -4489,7 +4507,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             int assocfiles = 0;
             if (filesmatched.size() > 0)
             {
-              boolean autoAssociate = Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false);
+              boolean autoAssociate = Cache
+                      .getDefault("AUTOASSOCIATE_PDBANDSEQS", false);
               if (!autoAssociate)
               {
                 String msg = MessageManager.formatMessage(
index 6c934c8..ea7fb6b 100644 (file)
@@ -326,7 +326,7 @@ public class AppJmol extends StructureViewerBase
     for (String s : files)
     {
       fileList.append(SPACE).append(QUOTE)
-              .append(Platform.escapeString(s)).append(QUOTE);
+              .append(Platform.escapeBackslashes(s)).append(QUOTE);
     }
     String filesString = fileList.toString();
 
@@ -515,7 +515,7 @@ public class AppJmol extends StructureViewerBase
             addingStructures = true; // already files loaded.
             for (int c = 0; c < filesInViewer.length; c++)
             {
-              if (filesInViewer[c].equals(file))
+              if (Platform.pathEquals(filesInViewer[c], file))
               {
                 file = null;
                 break;
index 9d68af1..65c8b5a 100644 (file)
@@ -538,7 +538,7 @@ public class BlogReader extends JPanel
     public LaunchJvBrowserOnItem(JList listItems)
     {
       super("Open in Browser");
-      this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
+      this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
       this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
       _listItems = listItems;
     }
index d07a7c2..a7349b8 100644 (file)
@@ -480,7 +480,7 @@ public class ChimeraViewFrame extends StructureViewerBase
         {
           filePDB.add(thePdbEntry);
           filePDBpos.add(Integer.valueOf(pi));
-          files.append(" \"" + Platform.escapeString(file) + "\"");
+          files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
         }
       }
     } catch (OutOfMemoryError oomerror)
index 9479ea6..6eab07d 100644 (file)
@@ -121,7 +121,7 @@ public class ColourMenuHelper
        */
       final String name = scheme.getSchemeName();
       String label = MessageManager.getStringOrReturn(
-              "label.colourScheme_", name.toLowerCase().replace(" ", "_"));
+              "label.colourScheme_", name);
       final JRadioButtonMenuItem radioItem = new JRadioButtonMenuItem(
               label);
       radioItem.setName(name);
index 2e51bce..1d7d1c9 100644 (file)
@@ -143,6 +143,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
   {
     textarea.setDocument(textarea.getEditorKit().createDefaultDocument());
     textarea.setText(text);
+    textarea.setCaretPosition(0);
   }
 
   @Override
index 1c87fd0..804e531 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.gui;
 
-import static jalview.util.UrlConstants.SEQUENCE_ID;
-
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
@@ -82,9 +80,11 @@ import java.beans.PropertyChangeListener;
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.ListIterator;
@@ -107,7 +107,6 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JDesktopPane;
-import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
@@ -146,6 +145,10 @@ public class Desktop extends jalview.jbgui.GDesktop
 
   private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
 
+  protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
+
+  public static HashMap<String, FileWriter> savingFiles = new HashMap<>();
+
   private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
 
   /**
@@ -347,14 +350,50 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     doConfigureStructurePrefs();
     setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
-    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    /*
+    if (!Platform.isAMac())
+    {
+      // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+    }
+    else
+    {
+     this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+    }
+    */
+
+    try
+    {
+      APQHandlers.setAPQHandlers(this);
+    } catch (Exception e)
+    {
+      System.out.println("Exception when trying to set APQHandlers");
+      e.printStackTrace();
+    } catch (Throwable t)
+    {
+      System.out.println("Throwable when trying to set APQHandlers");
+      t.printStackTrace();
+    }
+
+
+    addWindowListener(new WindowAdapter()
+    {
+
+      @Override
+      public void windowClosing(WindowEvent ev)
+      {
+        quit();
+      }
+    });
+
     boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
             false);
+
     boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
             false);
     desktop = new MyDesktopPane(selmemusage);
     showMemusage.setSelected(selmemusage);
     desktop.setBackground(Color.white);
+
     getContentPane().setLayout(new BorderLayout());
     // alternate config - have scrollbars - see notes in JAL-153
     // JScrollPane sp = new JScrollPane();
@@ -396,7 +435,15 @@ public class Desktop extends jalview.jbgui.GDesktop
                     + System.getProperty("java.version") + "\n"
                     + System.getProperty("os.arch") + " "
                     + System.getProperty("os.name") + " "
-                    + System.getProperty("os.version"));
+                    + System.getProperty("os.version")
+                    + (jalview.bin.Cache.getProperty("VERSION").equals("DEVELOPMENT")
+                                    ? "\nJava path:"
+                                            + System.getProperty(
+                                                    "java.home")
+                                            + File.separator + "bin"
+                                            + File.separator + "java"
+                                    : "")
+            );
 
     showConsole(showjconsole);
 
@@ -973,7 +1020,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
             InputEvent.CTRL_DOWN_MASK);
     KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
 
     InputMap inputMap = frame
             .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
@@ -1252,6 +1299,8 @@ public class Desktop extends jalview.jbgui.GDesktop
   @Override
   public void quit()
   {
+    //System.out.println("********** Desktop.quit()");
+    //System.out.println(savingFiles.toString());
     Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
     jalview.bin.Cache.setProperty("SCREENGEOMETRY_WIDTH",
             screen.width + "");
@@ -2411,7 +2460,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           while (li.hasNext())
           {
             String link = li.next();
-            if (link.contains(SEQUENCE_ID)
+            if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
                     && !UrlConstants.isDefaultString(link))
             {
               check = true;
@@ -2719,7 +2768,7 @@ public class Desktop extends jalview.jbgui.GDesktop
   {
     getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
             .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
-                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
+                    jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
                     "Quit");
     getRootPane().getActionMap().put("Quit", new AbstractAction()
     {
@@ -2776,18 +2825,18 @@ public class Desktop extends jalview.jbgui.GDesktop
       progressBarHandlers = new Hashtable<>();
     }
 
-    if (progressBars.get(new Long(id)) != null)
+    if (progressBars.get(Long.valueOf(id)) != null)
     {
-      JPanel panel = progressBars.remove(new Long(id));
-      if (progressBarHandlers.contains(new Long(id)))
+      JPanel panel = progressBars.remove(Long.valueOf(id));
+      if (progressBarHandlers.contains(Long.valueOf(id)))
       {
-        progressBarHandlers.remove(new Long(id));
+        progressBarHandlers.remove(Long.valueOf(id));
       }
       removeProgressPanel(panel);
     }
     else
     {
-      progressBars.put(new Long(id), addProgressPanel(message));
+      progressBars.put(Long.valueOf(id), addProgressPanel(message));
     }
   }
 
@@ -2802,13 +2851,13 @@ public class Desktop extends jalview.jbgui.GDesktop
           final IProgressIndicatorHandler handler)
   {
     if (progressBarHandlers == null
-            || !progressBars.containsKey(new Long(id)))
+            || !progressBars.containsKey(Long.valueOf(id)))
     {
       throw new Error(MessageManager.getString(
               "error.call_setprogressbar_before_registering_handler"));
     }
-    progressBarHandlers.put(new Long(id), handler);
-    final JPanel progressPanel = progressBars.get(new Long(id));
+    progressBarHandlers.put(Long.valueOf(id), handler);
+    final JPanel progressPanel = progressBars.get(Long.valueOf(id));
     if (handler.canCancel())
     {
       JButton cancel = new JButton(
index 46f574e..e22f506 100644 (file)
@@ -269,8 +269,8 @@ public class FeatureRenderer
             name.setText(sf.getType());
             description.setText(sf.getDescription());
             group.setText(sf.getFeatureGroup());
-            start.setValue(new Integer(sf.getBegin()));
-            end.setValue(new Integer(sf.getEnd()));
+            start.setValue(Integer.valueOf(sf.getBegin()));
+            end.setValue(Integer.valueOf(sf.getEnd()));
 
             SearchResultsI highlight = new SearchResults();
             highlight.addResult(sequences.get(0), sf.getBegin(),
@@ -357,8 +357,8 @@ public class FeatureRenderer
     name.setText(featureType);
     group.setText(featureGroup);
 
-    start.setValue(new Integer(firstFeature.getBegin()));
-    end.setValue(new Integer(firstFeature.getEnd()));
+    start.setValue(Integer.valueOf(firstFeature.getBegin()));
+    end.setValue(Integer.valueOf(firstFeature.getEnd()));
     description.setText(firstFeature.getDescription());
     updateColourButton(mainPanel, colour,
             (oldcol = fcol = getFeatureStyle(featureType)));
index 4526517..9ca409b 100644 (file)
@@ -636,7 +636,7 @@ public class FeatureSettings extends JPanel
         data[dataIndex][FILTER_COLUMN] = featureFilter == null
                 ? new FeatureMatcherSet()
                 : featureFilter;
-        data[dataIndex][SHOW_COLUMN] = new Boolean(
+        data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
                 af.getViewport().getFeaturesDisplayed().isVisible(type));
         dataIndex++;
         displayableTypes.remove(type);
@@ -663,7 +663,7 @@ public class FeatureSettings extends JPanel
       data[dataIndex][FILTER_COLUMN] = featureFilter == null
               ? new FeatureMatcherSet()
               : featureFilter;
-      data[dataIndex][SHOW_COLUMN] = new Boolean(true);
+      data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
       dataIndex++;
       displayableTypes.remove(type);
     }
index 82e826f..a2194f4 100644 (file)
@@ -63,11 +63,10 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JSlider;
 import javax.swing.JTextField;
-import javax.swing.SwingConstants;
+import javax.swing.border.EmptyBorder;
 import javax.swing.border.LineBorder;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.plaf.basic.BasicArrowButton;
 
 /**
  * A dialog where the user can configure colour scheme, and any filters, for one
@@ -1419,10 +1418,10 @@ public class FeatureTypeSettings extends JalviewDialog
     if (!patternField.isEnabled()
             || (pattern != null && pattern.trim().length() > 0))
     {
-      // todo: gif for button drawing '-' or 'x'
-      JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
-      removeCondition
-              .setToolTipText(MessageManager.getString("label.delete_row"));
+      JButton removeCondition = new JButton("\u2717"); // Dingbats cursive x
+      removeCondition.setToolTipText(
+              MessageManager.getString("label.delete_condition"));
+      removeCondition.setBorder(new EmptyBorder(0, 0, 0, 0));
       removeCondition.addActionListener(new ActionListener()
       {
         @Override
index 3388d4d..4f660a2 100644 (file)
@@ -601,11 +601,11 @@ public class PCAPanel extends GPCAPanel
     // }
     //
     // JPanel progressPanel;
-    // Long lId = new Long(id);
+    // Long lId = Long.valueOf(id);
     // GridLayout layout = (GridLayout) statusPanel.getLayout();
     // if (progressBars.get(lId) != null)
     // {
-    // progressPanel = (JPanel) progressBars.get(new Long(id));
+    // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
     // statusPanel.remove(progressPanel);
     // progressBars.remove(lId);
     // progressPanel = null;
@@ -644,13 +644,13 @@ public class PCAPanel extends GPCAPanel
           final IProgressIndicatorHandler handler)
   {
     progressBar.registerHandler(id, handler);
-    // if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
+    // if (progressBarHandlers == null || !progressBars.contains(Long.valueOf(id)))
     // {
     // throw new
     // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
     // }
-    // progressBarHandlers.put(new Long(id), handler);
-    // final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
+    // progressBarHandlers.put(Long.valueOf(id), handler);
+    // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
     // if (handler.canCancel())
     // {
     // JButton cancel = new JButton(
index 86febed..702773b 100644 (file)
@@ -78,10 +78,8 @@ import javax.swing.JPopupMenu;
 import javax.swing.JRadioButtonMenuItem;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision: 1.118 $
+ * The popup menu that is displayed on right-click on a sequence id, or in the
+ * sequence alignment.
  */
 public class PopupMenu extends JPopupMenu implements ColourChangeListener
 {
@@ -178,6 +176,187 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   JMenuItem hideInsertions = new JMenuItem();
 
   /**
+   * Constructs a menu with sub-menu items for any hyperlinks for the sequence
+   * and/or features provided. Hyperlinks may include a lookup by sequence id,
+   * or database cross-references, depending on which links are enabled in user
+   * preferences.
+   * 
+   * @param seq
+   * @param features
+   * @return
+   */
+  static JMenu buildLinkMenu(final SequenceI seq,
+          List<SequenceFeature> features)
+  {
+    JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
+
+    List<String> nlinks = null;
+    if (seq != null)
+    {
+      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+      UrlLink.sort(nlinks);
+    }
+    else
+    {
+      nlinks = new ArrayList<>();
+    }
+
+    if (features != null)
+    {
+      for (SequenceFeature sf : features)
+      {
+        if (sf.links != null)
+        {
+          for (String link : sf.links)
+          {
+            nlinks.add(link);
+          }
+        }
+      }
+    }
+
+    /*
+     * instantiate the hyperlinklink templates from sequence data;
+     * note the order of the templates is preserved in the map
+     */
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
+    for (String link : nlinks)
+    {
+      UrlLink urlLink = null;
+      try
+      {
+        urlLink = new UrlLink(link);
+      } catch (Exception foo)
+      {
+        Cache.log.error("Exception for URLLink '" + link + "'", foo);
+        continue;
+      }
+
+      if (!urlLink.isValid())
+      {
+        Cache.log.error(urlLink.getInvalidMessage());
+        continue;
+      }
+
+      urlLink.createLinksFromSeq(seq, linkset);
+    }
+
+    /*
+     * construct menu items for the hyperlinks (still preserving
+     * the order of the sorted templates)
+     */
+    addUrlLinks(linkMenu, linkset.values());
+
+    return linkMenu;
+  }
+
+  /**
+   * A helper method that builds menu items from the given links, with action
+   * handlers to open the link URL, and adds them to the linkMenu. Each provided
+   * link should be a list whose second item is the menu text, and whose fourth
+   * item is the URL to open when the menu item is selected.
+   * 
+   * @param linkMenu
+   * @param linkset
+   */
+  static private void addUrlLinks(JMenu linkMenu,
+          Collection<List<String>> linkset)
+  {
+    for (List<String> linkstrset : linkset)
+    {
+      final String url = linkstrset.get(3);
+      JMenuItem item = new JMenuItem(linkstrset.get(1));
+      item.setToolTipText(MessageManager
+              .formatMessage("label.open_url_param", new Object[]
+              { url }));
+      item.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          new Thread(new Runnable()
+          {
+            @Override
+            public void run()
+            {
+              showLink(url);
+            }
+          }).start();
+        }
+      });
+      linkMenu.add(item);
+    }
+  }
+
+  /**
+   * Opens the provided url in the default web browser, or shows an error
+   * message if this fails
+   * 
+   * @param url
+   */
+  static void showLink(String url)
+  {
+    try
+    {
+      jalview.util.BrowserLauncher.openURL(url);
+    } catch (Exception ex)
+    {
+      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+              MessageManager.getString("label.web_browser_not_found_unix"),
+              MessageManager.getString("label.web_browser_not_found"),
+              JvOptionPane.WARNING_MESSAGE);
+
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * add a late bound groupURL item to the given linkMenu
+   * 
+   * @param linkMenu
+   * @param label
+   *          - menu label string
+   * @param urlgenerator
+   *          GroupURLLink used to generate URL
+   * @param urlstub
+   *          Object array returned from the makeUrlStubs function.
+   */
+  static void addshowLink(JMenu linkMenu, String label,
+          final GroupUrlLink urlgenerator, final Object[] urlstub)
+  {
+    JMenuItem item = new JMenuItem(label);
+    item.setToolTipText(MessageManager
+            .formatMessage("label.open_url_seqs_param", new Object[]
+            { urlgenerator.getUrl_prefix(),
+                urlgenerator.getNumberInvolved(urlstub) }));
+    // TODO: put in info about what is being sent.
+    item.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        new Thread(new Runnable()
+        {
+
+          @Override
+          public void run()
+          {
+            try
+            {
+              showLink(urlgenerator.constructFrom(urlstub));
+            } catch (UrlStringTooLongException e2)
+            {
+            }
+          }
+
+        }).start();
+      }
+    });
+
+    linkMenu.add(item);
+  }
+
+  /**
    * Creates a new PopupMenu object.
    * 
    * @param ap
@@ -620,63 +799,14 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * When seq is not null, these are links for the sequence id, which may be to
    * external web sites for the sequence accession, and/or links embedded in
    * non-positional features. When seq is null, only links embedded in the
-   * provided features are added.
+   * provided features are added. If no links are found, the menu is not added.
    * 
    * @param seq
    * @param features
    */
   void addLinks(final SequenceI seq, List<SequenceFeature> features)
   {
-    JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
-
-    List<String> nlinks = null;
-    if (seq != null)
-    {
-      nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
-    }
-    else
-    {
-      nlinks = new ArrayList<>();
-    }
-
-    if (features != null)
-    {
-      for (SequenceFeature sf : features)
-      {
-        if (sf.links != null)
-        {
-          for (String link : sf.links)
-          {
-            nlinks.add(link);
-          }
-        }
-      }
-    }
-
-    Map<String, List<String>> linkset = new LinkedHashMap<>();
-
-    for (String link : nlinks)
-    {
-      UrlLink urlLink = null;
-      try
-      {
-        urlLink = new UrlLink(link);
-      } catch (Exception foo)
-      {
-        Cache.log.error("Exception for URLLink '" + link + "'", foo);
-        continue;
-      }
-
-      if (!urlLink.isValid())
-      {
-        Cache.log.error(urlLink.getInvalidMessage());
-        continue;
-      }
-
-      urlLink.createLinksFromSeq(seq, linkset);
-    }
-
-    addshowLinks(linkMenu, linkset.values());
+    JMenu linkMenu = buildLinkMenu(seq, features);
 
     // only add link menu if it has entries
     if (linkMenu.getItemCount() > 0)
@@ -969,98 +1099,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     }
   }
 
-  private void addshowLinks(JMenu linkMenu,
-          Collection<List<String>> linkset)
-  {
-    for (List<String> linkstrset : linkset)
-    {
-      // split linkstr into label and url
-      addshowLink(linkMenu, linkstrset.get(1), linkstrset.get(3));
-    }
-  }
-
-  /**
-   * add a show URL menu item to the given linkMenu
-   * 
-   * @param linkMenu
-   * @param label
-   *          - menu label string
-   * @param url
-   *          - url to open
-   */
-  private void addshowLink(JMenu linkMenu, String label, final String url)
-  {
-    JMenuItem item = new JMenuItem(label);
-    item.setToolTipText(MessageManager.formatMessage("label.open_url_param",
-            new Object[]
-            { url }));
-    item.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        new Thread(new Runnable()
-        {
-
-          @Override
-          public void run()
-          {
-            showLink(url);
-          }
-
-        }).start();
-      }
-    });
-
-    linkMenu.add(item);
-  }
-
-  /**
-   * add a late bound groupURL item to the given linkMenu
-   * 
-   * @param linkMenu
-   * @param label
-   *          - menu label string
-   * @param urlgenerator
-   *          GroupURLLink used to generate URL
-   * @param urlstub
-   *          Object array returned from the makeUrlStubs function.
-   */
-  private void addshowLink(JMenu linkMenu, String label,
-          final GroupUrlLink urlgenerator, final Object[] urlstub)
-  {
-    JMenuItem item = new JMenuItem(label);
-    item.setToolTipText(MessageManager
-            .formatMessage("label.open_url_seqs_param", new Object[]
-            { urlgenerator.getUrl_prefix(),
-                urlgenerator.getNumberInvolved(urlstub) }));
-    // TODO: put in info about what is being sent.
-    item.addActionListener(new ActionListener()
-    {
-      @Override
-      public void actionPerformed(ActionEvent e)
-      {
-        new Thread(new Runnable()
-        {
-
-          @Override
-          public void run()
-          {
-            try
-            {
-              showLink(urlgenerator.constructFrom(urlstub));
-            } catch (UrlStringTooLongException e2)
-            {
-            }
-          }
-
-        }).start();
-      }
-    });
-
-    linkMenu.add(item);
-  }
-
   /**
    * DOCUMENT ME!
    * 
@@ -1940,22 +1978,6 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     refresh();
   }
 
-  public void showLink(String url)
-  {
-    try
-    {
-      jalview.util.BrowserLauncher.openURL(url);
-    } catch (Exception ex)
-    {
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.web_browser_not_found_unix"),
-              MessageManager.getString("label.web_browser_not_found"),
-              JvOptionPane.WARNING_MESSAGE);
-
-      ex.printStackTrace();
-    }
-  }
-
   void hideSequences(boolean representGroup)
   {
     ap.av.hideSequences(sequence, representGroup);
index dc0cdb4..561fb3c 100755 (executable)
@@ -630,7 +630,7 @@ public class RotatableCanvas extends JPanel implements MouseListener,
     int yDelta = yPos - mouseY;
 
     // Check if this is a rectangle drawing drag
-    if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
+    if ((evt.getModifiersEx() & InputEvent.BUTTON2_DOWN_MASK) != 0)
     {
       // rectx2 = evt.getX();
       // recty2 = evt.getY();
index b95c569..8f49c6c 100755 (executable)
@@ -115,15 +115,7 @@ public class ScalePanel extends JPanel
     {
       x = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(x);
     }
-
-    if (x >= av.getAlignment().getWidth())
-    {
-      res = av.getAlignment().getWidth() - 1;
-    }
-    else
-    {
-      res = x;
-    }
+    res = Math.min(x, av.getAlignment().getWidth() - 1);
 
     min = res;
     max = res;
@@ -157,10 +149,27 @@ public class ScalePanel extends JPanel
    */
   protected void rightMouseButtonPressed(MouseEvent evt, final int res)
   {
+    JPopupMenu pop = buildPopupMenu(res);
+    if (pop.getSubElements().length > 0)
+    {
+      pop.show(this, evt.getX(), evt.getY());
+    }
+  }
+
+  /**
+   * Builds a popup menu with 'Hide' or 'Reveal' options, or both, or neither
+   * 
+   * @param res
+   *          column number (0..)
+   * @return
+   */
+  protected JPopupMenu buildPopupMenu(final int res)
+  {
     JPopupMenu pop = new JPopupMenu();
 
     /*
-     * grab the hidden range in case mouseMoved nulls it
+     * logic here depends on 'reveal', set in mouseMoved;
+     * grab the hidden range in case mouseMoved nulls it later
      */
     final int[] hiddenRange = reveal;
     if (hiddenRange != null)
@@ -198,9 +207,9 @@ public class ScalePanel extends JPanel
         });
         pop.add(item);
       }
-      pop.show(this, evt.getX(), evt.getY());
     }
-    else if (av.getColumnSelection().contains(res))
+
+    if (av.getColumnSelection().contains(res))
     {
       JMenuItem item = new JMenuItem(
               MessageManager.getString("label.hide_columns"));
@@ -222,8 +231,8 @@ public class ScalePanel extends JPanel
         }
       });
       pop.add(item);
-      pop.show(this, evt.getX(), evt.getY());
     }
+    return pop;
   }
 
   /**
@@ -279,14 +288,15 @@ public class ScalePanel extends JPanel
     ap.getSeqPanel().stopScrolling();
 
     int xCords = Math.max(0, evt.getX()); // prevent negative X coordinates
+    ViewportRanges ranges = av.getRanges();
     int res = (xCords / av.getCharWidth())
-            + av.getRanges().getStartRes();
+            + ranges.getStartRes();
+    res = Math.min(res, ranges.getEndRes());
     if (av.hasHiddenColumns())
     {
       res = av.getAlignment().getHiddenColumns()
               .visibleToAbsoluteColumn(res);
     }
-    res = Math.min(res, av.getRanges().getEndRes());
     res = Math.max(0, res);
 
     if (!stretchingGroup)
index 4a1a9ee..1176df5 100644 (file)
@@ -314,8 +314,10 @@ public class SeqPanel extends JPanel
     }
     else
     {
-      seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(),
+      ViewportRanges ranges = av.getRanges();
+      seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(),
               alignmentHeight - 1);
+      seqIndex = Math.min(seqIndex, ranges.getEndSeq());
     }
 
     return new MousePos(col, seqIndex, annIndex);
@@ -1924,6 +1926,7 @@ public class SeqPanel extends JPanel
   @Override
   public void mouseExited(MouseEvent e)
   {
+    lastMousePosition = null;
     ap.alignFrame.setStatus(" ");
     if (av.getWrapAlignment())
     {
index 81b394b..fb967ed 100755 (executable)
@@ -30,6 +30,9 @@ import java.awt.Color;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 
+import org.jfree.graphics2d.svg.SVGGraphics2D;
+import org.jibble.epsgraphics.EpsGraphics2D;
+
 public class SequenceRenderer implements jalview.api.SequenceRenderer
 {
   final static int CHAR_TO_UPPER = 'A' - 'a';
@@ -254,8 +257,19 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     }
     graphics.setColor(av.getTextColour());
 
-    if (monospacedFont && av.getShowText() && allGroups.length == 0
-            && !av.getColourText() && av.getThresholdTextColour() == 0)
+    boolean drawAllText = monospacedFont && av.getShowText() && allGroups.length == 0
+            && !av.getColourText() && av.getThresholdTextColour() == 0;
+
+    /*
+     * EPS or SVG misaligns monospaced strings (JAL-3239)
+     * so always draw these one character at a time
+     */
+    if (graphics instanceof EpsGraphics2D
+            || graphics instanceof SVGGraphics2D)
+    {
+      drawAllText = false;
+    }
+    if (drawAllText)
     {
       if (av.isRenderGaps())
       {
index a0d31cf..4a4c10c 100644 (file)
@@ -412,7 +412,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
      * Ctrl-W / Cmd-W - close view or window
      */
     KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     action = new AbstractAction()
     {
       @Override
@@ -433,7 +433,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
      * Ctrl-T / Cmd-T open new view
      */
     KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     AbstractAction action = new AbstractAction()
     {
       @Override
@@ -773,7 +773,7 @@ public class SplitFrame extends GSplitFrame implements SplitContainerI
      * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
      */
     KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     AbstractAction action = new AbstractAction()
     {
       @Override
index 850ef21..5186a26 100644 (file)
@@ -123,7 +123,7 @@ public class WsPreferences extends GWsPreferences
     for (String url : wsUrls)
     {
       int status = Jws2Discoverer.getDiscoverer().getServerStatusFor(url);
-      tdat[r][1] = new Integer(status);
+      tdat[r][1] = Integer.valueOf(status);
       tdat[r++][0] = url;
     }
 
index dd385d2..86b4402 100755 (executable)
@@ -274,7 +274,7 @@ public class AnnotationFile
           if (row.graphGroup > -1)
           {
             graphGroupSeen.set(row.graphGroup);
-            Integer key = new Integer(row.graphGroup);
+            Integer key = Integer.valueOf(row.graphGroup);
             if (graphGroup.containsKey(key))
             {
               graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
@@ -762,7 +762,7 @@ public class AnnotationFile
                       autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
                               (aa[aai].groupRef == null ? null
                                       : aa[aai].groupRef.getName())),
-                      new Integer(1));
+                      Integer.valueOf(1));
             }
           }
         }
@@ -1271,7 +1271,7 @@ public class AnnotationFile
         {
           displayChar = token;
           // foo
-          value = new Float(token).floatValue();
+          value = Float.valueOf(token).floatValue();
           parsedValue = true;
           continue;
         } catch (NumberFormatException ex)
index 12ad0d4..70f2ac7 100755 (executable)
@@ -441,7 +441,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
       float score = Float.NaN;
       try
       {
-        score = new Float(gffColumns[6]).floatValue();
+        score = Float.valueOf(gffColumns[6]).floatValue();
       } catch (NumberFormatException ex)
       {
         sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
index 791f881..3afbaad 100755 (executable)
@@ -193,6 +193,9 @@ public class FileLoader implements Runnable
         Thread.sleep(500);
       } catch (Exception ex)
       {
+        System.out.println(
+                "Exception caught while waiting for FileLoader thread");
+        ex.printStackTrace();
       }
     }
 
index 026c879..6732e82 100755 (executable)
@@ -207,7 +207,7 @@ public class JPredFile extends AlignFile
           {
             ascore = symbols.nextToken();
 
-            Float score = new Float(ascore);
+            Float score = Float.valueOf(ascore);
             scores.addElement(score);
           }
 
@@ -283,7 +283,7 @@ public class JPredFile extends AlignFile
 
           seq_entries.addElement(newseq.toString());
           ids.addElement(id);
-          Symscores.put(id, new Integer(ids.size() - 1));
+          Symscores.put(id, Integer.valueOf(ids.size() - 1));
         }
       }
     }
index c9f1fcf..6828202 100755 (executable)
@@ -148,7 +148,7 @@ public class JnetAnnotationMaker
             {
               for (int j = 0; j < width; j++)
               {
-                float value = new Float(preds[i].getCharAt(j) + "")
+                float value = Float.valueOf(preds[i].getCharAt(j) + "")
                         .floatValue();
                 annotations[gapmap[j]] = new Annotation(
                         preds[i].getCharAt(j) + "", "",
@@ -159,7 +159,7 @@ public class JnetAnnotationMaker
             {
               for (int j = 0; j < width; j++)
               {
-                float value = new Float(preds[i].getCharAt(j) + "")
+                float value = Float.valueOf(preds[i].getCharAt(j) + "")
                         .floatValue();
                 annotations[gapmap[delMap[j]]] = new Annotation(
                         preds[i].getCharAt(j) + "", "",
index 442278d..c1849fc 100755 (executable)
@@ -85,7 +85,7 @@ public class ModellerDescription
 
     resCode(int v)
     {
-      val = new Integer(v);
+      val = Integer.valueOf(v);
       field = val.toString();
     }
   };
index c414145..2221f00 100755 (executable)
@@ -477,7 +477,7 @@ public class NewickFile extends FileParse
           {
             try
             {
-              bootstrap = (new Integer(nbootstrap.stringMatched(1)))
+              bootstrap = (Integer.valueOf(nbootstrap.stringMatched(1)))
                       .intValue();
               HasBootstrap = true;
             } catch (Exception e)
@@ -494,7 +494,7 @@ public class NewickFile extends FileParse
         {
           try
           {
-            distance = (new Float(ndist.stringMatched(1))).floatValue();
+            distance = (Float.valueOf(ndist.stringMatched(1))).floatValue();
             HasDistances = true;
             nodehasdistance = true;
           } catch (Exception e)
@@ -661,7 +661,7 @@ public class NewickFile extends FileParse
             if (code.toLowerCase().equals("b"))
             {
               int v = -1;
-              Float iv = new Float(value);
+              Float iv = Float.valueOf(value);
               v = iv.intValue(); // jalview only does integer bootstraps
               // currently
               c.setBootstrap(v);
index adf4447..387dbfa 100644 (file)
@@ -2248,7 +2248,7 @@ public class VamsasAppDatastore
           Float val = null;
           try
           {
-            val = new Float(props[p].getContent());
+            val = Float.valueOf(props[p].getContent());
           } catch (Exception e)
           {
             Cache.log.warn("Failed to parse threshold property");
@@ -2537,7 +2537,7 @@ public class VamsasAppDatastore
           int se_end = se[1 - se[2]] + (se[2] == 0 ? 1 : -1);
           for (int p = se[se[2]]; p != se_end; p += se[2] == 0 ? 1 : -1)
           {
-            posList.add(new Integer(p));
+            posList.add(Integer.valueOf(p));
           }
         }
       }
@@ -2548,7 +2548,7 @@ public class VamsasAppDatastore
         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
         {
           pos = dseta.getPos(p).getI();
-          posList.add(new Integer(pos));
+          posList.add(Integer.valueOf(pos));
         }
       }
     }
index ff7a764..b53de08 100644 (file)
@@ -24,7 +24,7 @@ import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
 
-public class DatastoreRegistry
+public class DatastoreRegistry implements AutoCloseable
 {
   protected static org.apache.log4j.Logger log = org.apache.log4j.Logger
           .getLogger(DatastoreRegistry.class);
@@ -153,7 +153,7 @@ public class DatastoreRegistry
   }
 
   @Override
-  protected void finalize() throws Throwable
+  public void close()
   {
     if (dsObjReg != null)
     {
@@ -172,6 +172,6 @@ public class DatastoreRegistry
     {
       dsItemReg.clear();
     }
-    super.finalize();
+    // super.finalize();
   }
 }
index ee4fa83..b4580f0 100644 (file)
@@ -148,7 +148,7 @@ public abstract class Rangetype extends DatastoreItem
           int se_end = se[1 - se[2]] + (se[2] == 0 ? 1 : -1);
           for (int p = se[se[2]]; p != se_end; p += se[2] == 0 ? 1 : -1)
           {
-            posList.add(new Integer(p));
+            posList.add(Integer.valueOf(p));
           }
         }
       }
@@ -159,7 +159,7 @@ public abstract class Rangetype extends DatastoreItem
         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
         {
           pos = dseta.getPos(p).getI();
-          posList.add(new Integer(pos));
+          posList.add(Integer.valueOf(pos));
         }
       }
     }
@@ -193,8 +193,8 @@ public abstract class Rangetype extends DatastoreItem
         for (int s = 0, sSize = range.getSegCount(); s < sSize; s++)
         {
           se = getSegRange(range.getSeg(s), false);
-          posList.addElement(new Integer(se[0]));
-          posList.addElement(new Integer(se[1]));
+          posList.addElement(Integer.valueOf(se[0]));
+          posList.addElement(Integer.valueOf(se[1]));
         }
       }
       else if (range.getPosCount() > 0)
@@ -204,8 +204,8 @@ public abstract class Rangetype extends DatastoreItem
         for (int p = 0, pSize = range.getPosCount(); p < pSize; p++)
         {
           pos = range.getPos(p).getI();
-          posList.add(new Integer(pos));
-          posList.add(new Integer(pos));
+          posList.add(Integer.valueOf(pos));
+          posList.add(Integer.valueOf(pos));
         }
       }
     }
index 74f73d4..f8d86d5 100644 (file)
@@ -351,7 +351,7 @@ public class Sequencefeature extends Rangetype
         {
           try
           {
-            val = new Boolean(p.getContent());
+            val = Boolean.valueOf(p.getContent());
           } catch (Exception e)
           {
           }
@@ -360,7 +360,7 @@ public class Sequencefeature extends Rangetype
         {
           try
           {
-            val = new Float(p.getContent());
+            val = Float.valueOf(p.getContent());
 
           } catch (Exception e)
           {
@@ -370,7 +370,7 @@ public class Sequencefeature extends Rangetype
         {
           try
           {
-            val = new Integer(p.getContent());
+            val = Integer.valueOf(p.getContent());
           } catch (Exception e)
           {
           }
index aa130cc..9c1f5ea 100644 (file)
@@ -443,7 +443,7 @@ public class Tree extends DatastoreItem
     Integer nindx = (Integer) nodespecs.get(nname);
     if (nindx == null)
     {
-      nindx = new Integer(1);
+      nindx = Integer.valueOf(1);
     }
     nname = nindx.toString() + " " + nname;
     return nname;
@@ -465,7 +465,7 @@ public class Tree extends DatastoreItem
     String oval = nodespec.substring(0, nodespec.indexOf(' '));
     try
     {
-      occurence = new Integer(oval).intValue();
+      occurence = Integer.valueOf(oval).intValue();
     } catch (Exception e)
     {
       System.err.println("Invalid nodespec '" + nodespec + "'");
index 5ba5d93..9831af7 100644 (file)
@@ -51,6 +51,8 @@ import htsjdk.variant.vcf.VCFInfoHeaderLine;
  */
 public class VCFLoader
 {
+  private static final String DEFAULT_SPECIES = "homo_sapiens";
+
   /**
    * A class to model the mapping from sequence to VCF coordinates. Cases include
    * <ul>
@@ -82,7 +84,7 @@ public class VCFLoader
 
   /*
    * Lookup keys, and default values, for Preference entries that describe
-   * patterns for VCF and VEP fields to capture 
+   * patterns for VCF and VEP fields to capture
    */
   private static final String VEP_FIELDS_PREF = "VEP_FIELDS";
 
@@ -93,6 +95,16 @@ public class VCFLoader
   private static final String DEFAULT_VEP_FIELDS = ".*";// "Allele,Consequence,IMPACT,SWISSPROT,SIFT,PolyPhen,CLIN_SIG";
 
   /*
+   * Lookup keys, and default values, for Preference entries that give
+   * mappings from tokens in the 'reference' header to species or assembly
+   */
+  private static final String VCF_ASSEMBLY = "VCF_ASSEMBLY";
+
+  private static final String DEFAULT_VCF_ASSEMBLY = "assembly19=GRCh38,hs37=GRCh37,grch37=GRCh37,grch38=GRCh38";
+
+  private static final String VCF_SPECIES = "VCF_SPECIES"; // default is human
+
+  /*
    * keys to fields of VEP CSQ consequence data
    * see https://www.ensembl.org/info/docs/tools/vep/vep_formats.html
    */
@@ -114,12 +126,6 @@ public class VCFLoader
   private static final String PIPE_REGEX = "\\|";
 
   /*
-   * key for Allele Frequency output by VEP
-   * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html
-   */
-  private static final String ALLELE_FREQUENCY_KEY = "AF";
-
-  /*
    * delimiter that separates multiple consequence data blocks
    */
   private static final String COMMA = ",";
@@ -155,6 +161,16 @@ public class VCFLoader
   private VCFHeader header;
 
   /*
+   * species (as a valid Ensembl term) the VCF is for 
+   */
+  private String vcfSpecies;
+
+  /*
+   * genome assembly version (as a valid Ensembl identifier) the VCF is for 
+   */
+  private String vcfAssembly;
+
+  /*
    * a Dictionary of contigs (if present) referenced in the VCF file
    */
   private SAMSequenceDictionary dictionary;
@@ -246,12 +262,13 @@ public class VCFLoader
    */
   public SequenceI loadVCFContig(String contig)
   {
-    String ref = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY)
-            .getValue();
+    VCFHeaderLine headerLine = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
+    String ref = headerLine == null ? null : headerLine.getValue();
     if (ref.startsWith("file://"))
     {
       ref = ref.substring(7);
     }
+    setSpeciesAndAssembly(ref);
 
     SequenceI seq = null;
     File dbFile = new File(ref);
@@ -260,7 +277,7 @@ public class VCFLoader
     {
       HtsContigDb db = new HtsContigDb("", dbFile);
       seq = db.getSequenceProxy(contig);
-      loadSequenceVCF(seq, ref);
+      loadSequenceVCF(seq);
       db.close();
     }
     else
@@ -284,7 +301,9 @@ public class VCFLoader
     {
       VCFHeaderLine ref = header
               .getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
-      String vcfAssembly = ref.getValue();
+      String reference = ref.getValue();
+
+      setSpeciesAndAssembly(reference);
 
       int varCount = 0;
       int seqCount = 0;
@@ -294,7 +313,7 @@ public class VCFLoader
        */
       for (SequenceI seq : seqs)
       {
-        int added = loadSequenceVCF(seq, vcfAssembly);
+        int added = loadSequenceVCF(seq);
         if (added > 0)
         {
           seqCount++;
@@ -338,6 +357,65 @@ public class VCFLoader
   }
 
   /**
+   * Attempts to determine and save the species and genome assembly version to
+   * which the VCF data applies. This may be done by parsing the {@code reference}
+   * header line, configured in a property file, or (potentially) confirmed
+   * interactively by the user.
+   * <p>
+   * The saved values should be identifiers valid for Ensembl's REST service
+   * {@code map} endpoint, so they can be used (if necessary) to retrieve the
+   * mapping between VCF coordinates and sequence coordinates.
+   * 
+   * @param reference
+   * @see https://rest.ensembl.org/documentation/info/assembly_map
+   * @see https://rest.ensembl.org/info/assembly/human?content-type=text/xml
+   * @see https://rest.ensembl.org/info/species?content-type=text/xml
+   */
+  protected void setSpeciesAndAssembly(String reference)
+  {
+    reference = reference.toLowerCase();
+    vcfSpecies = DEFAULT_SPECIES;
+
+    /*
+     * for a non-human species, or other assembly identifier,
+     * specify as a Jalview property file entry e.g.
+     * VCF_ASSEMBLY = hs37=GRCh37,assembly19=GRCh37
+     * VCF_SPECIES = c_elegans=celegans
+     * to map a token in the reference header to a value
+     */
+    String prop = Cache.getDefault(VCF_ASSEMBLY, DEFAULT_VCF_ASSEMBLY);
+    for (String token : prop.split(","))
+    {
+      String[] tokens = token.split("=");
+      if (tokens.length == 2)
+      {
+        if (reference.contains(tokens[0].trim().toLowerCase()))
+        {
+          vcfAssembly = tokens[1].trim();
+          break;
+        }
+      }
+    }
+
+    prop = Cache.getProperty(VCF_SPECIES);
+    if (prop != null)
+    {
+      for (String token : prop.split(","))
+      {
+        String[] tokens = token.split("=");
+        if (tokens.length == 2)
+        {
+          if (reference.contains(tokens[0].trim().toLowerCase()))
+          {
+            vcfSpecies = tokens[1].trim();
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * Opens the VCF file and parses header data
    * 
    * @param filePath
@@ -588,12 +666,11 @@ public class VCFLoader
    * and returns the number of variant features added
    * 
    * @param seq
-   * @param vcfAssembly
    * @return
    */
-  protected int loadSequenceVCF(SequenceI seq, String vcfAssembly)
+  protected int loadSequenceVCF(SequenceI seq)
   {
-    VCFMap vcfMap = getVcfMap(seq, vcfAssembly);
+    VCFMap vcfMap = getVcfMap(seq);
     if (vcfMap == null)
     {
       return 0;
@@ -614,10 +691,9 @@ public class VCFLoader
    * Answers a map from sequence coordinates to VCF chromosome ranges
    * 
    * @param seq
-   * @param vcfAssembly
    * @return
    */
-  private VCFMap getVcfMap(SequenceI seq, String vcfAssembly)
+  private VCFMap getVcfMap(SequenceI seq)
   {
     /*
      * simplest case: sequence has id and length matching a VCF contig
@@ -650,32 +726,26 @@ public class VCFLoader
     String seqRef = seqCoords.getAssemblyId();
     MapList map = seqCoords.getMap();
 
-    if (!vcfSpeciesMatchesSequence(vcfAssembly, species))
+    // note this requires the configured species to match that
+    // returned with the Ensembl sequence; todo: support aliases?
+    if (!vcfSpecies.equalsIgnoreCase(species))
     {
+      Cache.log.warn("No VCF loaded to " + seq.getName()
+              + " as species not matched");
       return null;
     }
 
-    if (vcfAssemblyMatchesSequence(vcfAssembly, seqRef))
+    if (seqRef.equalsIgnoreCase(vcfAssembly))
     {
       return new VCFMap(chromosome, map);
     }
 
-    if (!"GRCh38".equalsIgnoreCase(seqRef) // Ensembl
-            || !vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD
-    {
-      return null;
-    }
-
     /*
-     * map chromosomal coordinates from sequence to VCF if the VCF
-     * data has a different reference assembly to the sequence
+     * VCF data has a different reference assembly to the sequence:
+     * query Ensembl to map chromosomal coordinates from sequence to VCF
      */
-    // TODO generalise for cases other than GRCh38 -> GRCh37 !
-    // - or get the user to choose in a dialog
-
     List<int[]> toVcfRanges = new ArrayList<>();
     List<int[]> fromSequenceRanges = new ArrayList<>();
-    String toRef = "GRCh37";
 
     for (int[] range : map.getToRanges())
     {
@@ -687,12 +757,13 @@ public class VCFLoader
       }
 
       int[] newRange = mapReferenceRange(range, chromosome, "human", seqRef,
-              toRef);
+              vcfAssembly);
       if (newRange == null)
       {
         Cache.log.error(
                 String.format("Failed to map %s:%s:%s:%d:%d to %s", species,
-                        chromosome, seqRef, range[0], range[1], toRef));
+                        chromosome, seqRef, range[0], range[1],
+                        vcfAssembly));
         continue;
       }
       else
@@ -733,62 +804,6 @@ public class VCFLoader
   }
 
   /**
-   * Answers true if we determine that the VCF data uses the same reference
-   * assembly as the sequence, else false
-   * 
-   * @param vcfAssembly
-   * @param seqRef
-   * @return
-   */
-  private boolean vcfAssemblyMatchesSequence(String vcfAssembly,
-          String seqRef)
-  {
-    // TODO improve on this stub, which handles gnomAD and
-    // hopes for the best for other cases
-
-    if ("GRCh38".equalsIgnoreCase(seqRef) // Ensembl
-            && vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD
-    {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Answers true if the species inferred from the VCF reference identifier
-   * matches that for the sequence
-   * 
-   * @param vcfAssembly
-   * @param speciesId
-   * @return
-   */
-  boolean vcfSpeciesMatchesSequence(String vcfAssembly, String speciesId)
-  {
-    // PROBLEM 1
-    // there are many aliases for species - how to equate one with another?
-    // PROBLEM 2
-    // VCF ##reference header is an unstructured URI - how to extract species?
-    // perhaps check if ref includes any (Ensembl) alias of speciesId??
-    // TODO ask the user to confirm this??
-
-    if (vcfAssembly.contains("Homo_sapiens") // gnomAD exome data example
-            && "HOMO_SAPIENS".equals(speciesId)) // Ensembl species id
-    {
-      return true;
-    }
-
-    if (vcfAssembly.contains("c_elegans") // VEP VCF response example
-            && "CAENORHABDITIS_ELEGANS".equals(speciesId)) // Ensembl
-    {
-      return true;
-    }
-
-    // this is not a sustainable solution...
-
-    return false;
-  }
-
-  /**
    * Queries the VCF reader for any variants that overlap the mapped chromosome
    * ranges of the sequence, and adds as variant features. Returns the number of
    * overlapping variants found.
index 9f41c92..075b490 100755 (executable)
@@ -35,7 +35,6 @@ import jalview.util.Platform;
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.GridLayout;
-import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
@@ -266,14 +265,14 @@ public class GAlignFrame extends JInternalFrame
 
     // FIXME getDefaultToolkit throws an exception in Headless mode
     KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                    | KeyEvent.SHIFT_MASK,
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                    | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
             false);
     addMenuActionAndAccelerator(keyStroke, saveAs, al);
 
     closeMenuItem.setText(MessageManager.getString("action.close"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -296,7 +295,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem selectAllSequenceMenuItem = new JMenuItem(
             MessageManager.getString("action.select_all"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -323,7 +322,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem invertSequenceMenuItem = new JMenuItem(
             MessageManager.getString("action.invert_sequence_selection"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -359,7 +358,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem remove2LeftMenuItem = new JMenuItem(
             MessageManager.getString("action.remove_left"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_L,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -373,7 +372,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem remove2RightMenuItem = new JMenuItem(
             MessageManager.getString("action.remove_right"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -387,7 +386,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem removeGappedColumnMenuItem = new JMenuItem(
             MessageManager.getString("action.remove_empty_columns"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -401,8 +400,8 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem removeAllGapsMenuItem = new JMenuItem(
             MessageManager.getString("action.remove_all_gaps"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                    | KeyEvent.SHIFT_MASK,
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                    | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
             false);
     al = new ActionListener()
     {
@@ -509,7 +508,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem removeRedundancyMenuItem = new JMenuItem(
             MessageManager.getString("action.remove_redundancy"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -684,7 +683,7 @@ public class GAlignFrame extends JInternalFrame
     undoMenuItem.setEnabled(false);
     undoMenuItem.setText(MessageManager.getString("action.undo"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -698,7 +697,7 @@ public class GAlignFrame extends JInternalFrame
     redoMenuItem.setEnabled(false);
     redoMenuItem.setText(MessageManager.getString("action.redo"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -722,7 +721,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem printMenuItem = new JMenuItem(
             MessageManager.getString("action.print"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -748,7 +747,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem findMenuItem = new JMenuItem(
             MessageManager.getString("action.find"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     findMenuItem.setToolTipText(JvSwingUtils.wrapTooltip(true,
             MessageManager.getString("label.find_tip")));
     al = new ActionListener()
@@ -909,7 +908,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem deleteGroups = new JMenuItem(
             MessageManager.getString("action.undefine_groups"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -934,7 +933,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem createGroup = new JMenuItem(
             MessageManager.getString("action.create_group"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -948,8 +947,8 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem unGroup = new JMenuItem(
             MessageManager.getString("action.remove_group"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                    | KeyEvent.SHIFT_MASK,
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                    | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
             false);
     al = new ActionListener()
     {
@@ -963,7 +962,7 @@ public class GAlignFrame extends JInternalFrame
 
     copy.setText(MessageManager.getString("action.copy"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
 
     al = new ActionListener()
     {
@@ -977,7 +976,7 @@ public class GAlignFrame extends JInternalFrame
 
     cut.setText(MessageManager.getString("action.cut"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -1003,8 +1002,8 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem pasteNew = new JMenuItem(
             MessageManager.getString("label.to_new_alignment"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                    | KeyEvent.SHIFT_MASK,
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                    | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
             false);
     al = new ActionListener()
     {
@@ -1019,7 +1018,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem pasteThis = new JMenuItem(
             MessageManager.getString("label.to_this_alignment"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -1531,8 +1530,8 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem invertColSel = new JMenuItem(
             MessageManager.getString("action.invert_column_selection"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                    | KeyEvent.ALT_MASK,
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                    | jalview.util.ShortcutKeyMaskExWrapper.ALT_DOWN_MASK,
             false);
     al = new ActionListener()
     {
@@ -1595,7 +1594,7 @@ public class GAlignFrame extends JInternalFrame
 
     JMenuItem save = new JMenuItem(MessageManager.getString("action.save"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
@@ -1620,7 +1619,7 @@ public class GAlignFrame extends JInternalFrame
     JMenuItem newView = new JMenuItem(
             MessageManager.getString("action.new_view"));
     keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
     al = new ActionListener()
     {
       @Override
index a6e0ace..3f0df21 100644 (file)
@@ -22,10 +22,10 @@ package jalview.jbgui;
 
 import jalview.gui.JvSwingUtils;
 import jalview.util.MessageManager;
+import jalview.util.ShortcutKeyMaskExWrapper;
 
 import java.awt.BorderLayout;
 import java.awt.Font;
-import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
@@ -136,11 +136,11 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame
     });
     close.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_W,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     selectAll.setText(MessageManager.getString("action.select_all"));
     selectAll.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_A,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     selectAll.addActionListener(new ActionListener()
     {
       @Override
@@ -153,7 +153,7 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame
     save.setText(MessageManager.getString("action.save"));
     save.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_S,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     save.addActionListener(new ActionListener()
     {
       @Override
@@ -164,7 +164,7 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame
     });
     copyItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_C,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
 
     editMenubar.add(jMenu1);
     editMenubar.add(editMenu);
index 97ac840..94a4677 100755 (executable)
@@ -123,7 +123,7 @@ public class GCutAndPasteTransfer extends JInternalFrame
     selectAll.setText(MessageManager.getString("action.select_all"));
     selectAll.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_A,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     selectAll.addActionListener(new ActionListener()
     {
       @Override
@@ -136,7 +136,7 @@ public class GCutAndPasteTransfer extends JInternalFrame
     save.setText(MessageManager.getString("action.save"));
     save.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_S,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     save.addActionListener(new ActionListener()
     {
       @Override
@@ -147,10 +147,10 @@ public class GCutAndPasteTransfer extends JInternalFrame
     });
     copyItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_C,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     pasteMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(
             java.awt.event.KeyEvent.VK_V,
-            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
+            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false));
     editMenubar.add(jMenu1);
     editMenubar.add(editMenu);
     textarea.setFont(new java.awt.Font("Monospaced", Font.PLAIN, 12));
index 846107d..f30fa9b 100755 (executable)
@@ -26,7 +26,6 @@ import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 import java.awt.FlowLayout;
-import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
@@ -161,7 +160,7 @@ public class GDesktop extends JFrame
             .setText(MessageManager.getString("label.load_tree_from_file"));
     inputLocalFileMenuItem.setAccelerator(
             javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O,
-                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
+                    jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(),
                     false));
     inputLocalFileMenuItem
             .addActionListener(new java.awt.event.ActionListener()
@@ -425,6 +424,8 @@ public class GDesktop extends JFrame
       }
     });
 
+    Float specversion = Float.parseFloat(System.getProperty("java.specification.version"));
+    
     desktopMenubar.add(FileMenu);
     desktopMenubar.add(toolsMenu);
     VamsasMenu.setVisible(false);
@@ -447,7 +448,10 @@ public class GDesktop extends JFrame
     VamsasMenu.add(vamsasImport);
     VamsasMenu.add(vamsasSave);
     VamsasMenu.add(vamsasStop);
-    toolsMenu.add(preferences);
+    if (!Platform.isAMac() || specversion < 11)
+    {
+      toolsMenu.add(preferences);
+    }
     toolsMenu.add(showMemusage);
     toolsMenu.add(showConsole);
     toolsMenu.add(showNews);
@@ -550,6 +554,7 @@ public class GDesktop extends JFrame
    */
   protected void quit()
   {
+    //System.out.println("********** GDesktop.quit()");
   }
 
   /**
index 02b405e..0d2ec68 100644 (file)
@@ -4520,7 +4520,7 @@ public class Jalview2XML
           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
           filedat = oldFiles.get(new File(reformatedOldFilename));
         }
-        newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+        newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
         pdbfilenames.add(filedat.getFilePath());
         pdbids.add(filedat.getPdbId());
         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
@@ -5113,7 +5113,7 @@ public class Jalview2XML
         }
         else
         {
-          featureOrder.put(featureType, new Float(
+          featureOrder.put(featureType, Float.valueOf(
                   fs / jm.getFeatureSettings().getSetting().size()));
         }
         if (safeBoolean(setting.isDisplay()))
@@ -5125,7 +5125,7 @@ public class Jalview2XML
       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
       {
         Group grp = jm.getFeatureSettings().getGroup().get(gs);
-        fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
+        fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
       }
       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
index 42465f2..d31fbba 100644 (file)
@@ -72,12 +72,16 @@ public class ColourSchemes
     {
       try
       {
-        registerColourScheme(cs.getSchemeClass().newInstance());
+        registerColourScheme(
+                cs.getSchemeClass().getDeclaredConstructor().newInstance());
       } catch (InstantiationException | IllegalAccessException e)
       {
         System.err.println("Error instantiating colour scheme for "
                 + cs.toString() + " " + e.getMessage());
         e.printStackTrace();
+      } catch (ReflectiveOperationException roe)
+      {
+        roe.printStackTrace();
       }
     }
   }
index f0d1990..9d2c738 100755 (executable)
@@ -53,8 +53,10 @@ public class Consensus
   /**
    * @deprecated Use {@link #isConserved(int[][],int,int,boolean)} instead
    */
+  @Deprecated
   public boolean isConserved(int[][] cons2, int col, int size)
   {
+    System.out.println("DEPRECATED!!!!");
     return isConserved(cons2, col, size, true);
   }
 
@@ -71,7 +73,7 @@ public class Consensus
       tot += cons2[col][mask[i]];
     }
 
-    if ((double) tot > ((threshold * size) / 100))
+    if (tot > ((threshold * size) / 100))
     {
       // System.out.println("True conserved "+tot+" from "+threshold+" out of
       // "+size+" : "+maskstr);
index 6483b85..0d36f4f 100644 (file)
@@ -323,7 +323,7 @@ public class FeatureColour implements FeatureColourI
       {
         if (minval.length() > 0)
         {
-          min = new Float(minval).floatValue();
+          min = Float.valueOf(minval).floatValue();
         }
       } catch (Exception e)
       {
@@ -335,7 +335,7 @@ public class FeatureColour implements FeatureColourI
       {
         if (maxval.length() > 0)
         {
-          max = new Float(maxval).floatValue();
+          max = Float.valueOf(maxval).floatValue();
         }
       } catch (Exception e)
       {
@@ -403,7 +403,7 @@ public class FeatureColour implements FeatureColourI
         {
           gcol.nextToken();
           tval = gcol.nextToken();
-          featureColour.setThreshold(new Float(tval).floatValue());
+          featureColour.setThreshold(Float.valueOf(tval).floatValue());
         } catch (Exception e)
         {
           System.err.println("Couldn't parse threshold value as a float: ("
index cd986c0..ff88bb7 100644 (file)
@@ -41,6 +41,7 @@ import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 import jalview.ws.sifts.SiftsClient;
 import jalview.ws.sifts.SiftsException;
 import jalview.ws.sifts.SiftsSettings;
@@ -1170,7 +1171,8 @@ public class StructureSelectionManager
     StringBuilder sb = new StringBuilder(64);
     for (StructureMapping sm : mappings)
     {
-      if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
+      if (Platform.pathEquals(sm.pdbfile, pdbfile)
+              && seqs.contains(sm.sequence))
       {
         sb.append(sm.mappingDetails);
         sb.append(NEWLINE);
@@ -1343,7 +1345,10 @@ public class StructureSelectionManager
         instances.remove(jalviewLite);
         try
         {
-          mnger.finalize();
+          /* bsoares 2019-03-20 finalize deprecated, no apparent external
+           * resources to close
+           */
+          // mnger.finalize();
         } catch (Throwable x)
         {
         }
index 86d5660..d23f771 100644 (file)
@@ -107,12 +107,12 @@ public class CustomUrlProvider extends UrlProviderImpl
   {
     // cachedUrlList is in form <label>|<url>|<label>|<url>...
     // parse cachedUrlList into labels (used as id) and url links
-    HashMap<String, UrlLink> urls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> urls = new HashMap<>();
 
     StringTokenizer st = new StringTokenizer(urlStrings, SEP);
     while (st.hasMoreElements())
     {
-      String name = st.nextToken();
+      String name = st.nextToken().trim();
 
       if (!isMiriamId(name))
       {
@@ -129,6 +129,7 @@ public class CustomUrlProvider extends UrlProviderImpl
         {
           url = url + SEP + st.nextToken();
         }
+        url = url.trim();
         urls.put(name, new UrlLink(name, url, name));
       }
     }
@@ -138,7 +139,7 @@ public class CustomUrlProvider extends UrlProviderImpl
 
   private HashMap<String, UrlLink> parseUrlList(Map<String, String> urlList)
   {
-    HashMap<String, UrlLink> urls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> urls = new HashMap<>();
     if (urlList == null)
     {
       return urls;
@@ -187,7 +188,7 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public List<String> getLinksForMenu()
   {
-    List<String> links = new ArrayList<String>();
+    List<String> links = new ArrayList<>();
     Iterator<Map.Entry<String, UrlLink>> it = selectedUrls.entrySet()
             .iterator();
     while (it.hasNext())
@@ -201,7 +202,7 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public List<UrlLinkDisplay> getLinksForTable()
   {
-    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<UrlLinkDisplay>();
+    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<>();
     displayLinks = getLinksForTable(selectedUrls, true);
     displayLinks.addAll(getLinksForTable(nonselectedUrls, false));
     return displayLinks;
@@ -289,8 +290,8 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public void setUrlData(List<UrlLinkDisplay> links)
   {
-    HashMap<String, UrlLink> unselurls = new HashMap<String, UrlLink>();
-    HashMap<String, UrlLink> selurls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> unselurls = new HashMap<>();
+    HashMap<String, UrlLink> selurls = new HashMap<>();
 
     Iterator<UrlLinkDisplay> it = links.iterator();
     while (it.hasNext())
index 7840261..7232042 100755 (executable)
@@ -78,22 +78,26 @@ public class ImageMaker
 
   public enum TYPE
   {
-    EPS("EPS", MessageManager.getString("label.eps_file"), getEPSChooser()),
+    EPS("EPS", MessageManager.getString("label.eps_file"), EPS_EXTENSION,
+            EPS_DESCRIPTION),
     PNG("PNG", MessageManager.getString("label.png_image"),
-            getPNGChooser()),
-    SVG("SVG", "SVG", getSVGChooser());
-
-    private JalviewFileChooser chooser;
+            PNG_EXTENSION, PNG_DESCRIPTION),
+    SVG("SVG", "SVG", SVG_EXTENSION, SVG_DESCRIPTION);
 
     private String name;
 
     private String label;
 
-    TYPE(String name, String label, JalviewFileChooser chooser)
+    private String extension;
+
+    private String description;
+
+    TYPE(String name, String label, String ext, String desc)
     {
       this.name = name;
       this.label = label;
-      this.chooser = chooser;
+      this.extension = ext;
+      this.description = desc;
     }
 
     public String getName()
@@ -101,9 +105,9 @@ public class ImageMaker
       return name;
     }
 
-    public JalviewFileChooser getChooser()
+    public JalviewFileChooser getFileChooser()
     {
-      return chooser;
+      return new JalviewFileChooser(extension, description);
     }
 
     public String getLabel()
@@ -126,7 +130,7 @@ public class ImageMaker
       setProgressMessage(MessageManager.formatMessage(
               "status.waiting_for_user_to_select_output_file", type.name));
       JalviewFileChooser chooser;
-      chooser = type.getChooser();
+      chooser = type.getFileChooser();
       chooser.setFileView(new jalview.io.JalviewFileView());
       chooser.setDialogTitle(title);
       chooser.setToolTipText(MessageManager.getString("action.save"));
index 2c74609..c1d8228 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.util;
 
-import java.awt.Toolkit;
 import java.awt.event.MouseEvent;
 
 /**
@@ -88,23 +87,15 @@ public class Platform
   }
 
   /**
-   * escape a string according to the local platform's escape character
+   * Answers the input with every backslash replaced with a double backslash (an
+   * 'escaped' single backslash)
    * 
-   * @param file
-   * @return escaped file
+   * @param s
+   * @return
    */
-  public static String escapeString(String file)
+  public static String escapeBackslashes(String s)
   {
-    StringBuffer f = new StringBuffer();
-    int p = 0, lastp = 0;
-    while ((p = file.indexOf('\\', lastp)) > -1)
-    {
-      f.append(file.subSequence(lastp, p));
-      f.append("\\\\");
-      lastp = p + 1;
-    }
-    f.append(file.substring(lastp));
-    return f.toString();
+    return s == null ? null : s.replace("\\", "\\\\");
   }
 
   /**
@@ -140,10 +131,32 @@ public class Platform
       {
         return false;
       }
-      return (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-              & e.getModifiers()) != 0;
-      // could we use e.isMetaDown() here?
+      return (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() // .getMenuShortcutKeyMaskEx()
+              & e.getModifiersEx()) != 0; // getModifiers()) != 0;
     }
     return e.isControlDown();
   }
+
+  /**
+   * A (case sensitive) file path comparator that ignores the difference between /
+   * and \
+   * 
+   * @param path1
+   * @param path2
+   * @return
+   */
+  public static boolean pathEquals(String path1, String path2)
+  {
+    if (path1 == null)
+    {
+      return path2 == null;
+    }
+    if (path2 == null)
+    {
+      return false;
+    }
+    String p1 = path1.replace('\\', '/');
+    String p2 = path2.replace('\\', '/');
+    return p1.equals(p2);
+  }
 }
diff --git a/src/jalview/util/ShortcutKeyMaskExWrapper.java b/src/jalview/util/ShortcutKeyMaskExWrapper.java
new file mode 100644 (file)
index 0000000..7292c80
--- /dev/null
@@ -0,0 +1,57 @@
+package jalview.util;
+
+import java.awt.Toolkit;
+import java.awt.event.KeyEvent;
+
+public class ShortcutKeyMaskExWrapper
+{
+
+  private static boolean init = false;
+
+  private static final Float specversion = Float
+          .parseFloat(System.getProperty("java.specification.version"));
+
+  private static final float modern = 11;
+
+  public static int SHIFT_DOWN_MASK = KeyEvent.SHIFT_DOWN_MASK;
+
+  public static int ALT_DOWN_MASK = KeyEvent.ALT_DOWN_MASK;
+
+  public ShortcutKeyMaskExWrapper()
+  {
+  }
+
+  private static void init()
+  {
+    if (init)
+    {
+      return;
+    }
+    if (specversion < modern)
+    {
+      SHIFT_DOWN_MASK = KeyEvent.SHIFT_MASK;
+      ALT_DOWN_MASK = KeyEvent.ALT_MASK;
+    }
+    else
+    {
+      SHIFT_DOWN_MASK = KeyEvent.SHIFT_DOWN_MASK;
+      ALT_DOWN_MASK = KeyEvent.ALT_DOWN_MASK;
+    }
+
+    init = true;
+  }
+
+  public static int getMenuShortcutKeyMaskEx()
+  {
+    init();
+    if (specversion < modern)
+    {
+      return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
+    }
+    else
+    {
+      return Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
+    }
+  }
+
+}
index 007da86..18ee9b6 100644 (file)
@@ -29,21 +29,57 @@ import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.SequenceI;
 
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
+/**
+ * A helper class to parse URL Link strings taken from applet parameters or
+ * jalview properties file using the com.stevesoft.pat.Regex implementation.
+ * Jalview 2.4 extension allows regular expressions to be used to parse ID
+ * strings and replace the result in the URL. Regex's operate on the whole ID
+ * string given to the matchURL method, if no regex is supplied, then only text
+ * following the first pipe symbol will be substituted. Usage documentation
+ * todo.
+ */
 public class UrlLink
 {
+  private static final String SEQUENCEID_PLACEHOLDER = DELIM + SEQUENCE_ID
+          + DELIM;
+
+  private static final String ACCESSION_PLACEHOLDER = DELIM + DB_ACCESSION
+          + DELIM;
+
   /**
-   * helper class to parse URL Link strings taken from applet parameters or
-   * jalview properties file using the com.stevesoft.pat.Regex implementation.
-   * Jalview 2.4 extension allows regular expressions to be used to parse ID
-   * strings and replace the result in the URL. Regex's operate on the whole ID
-   * string given to the matchURL method, if no regex is supplied, then only
-   * text following the first pipe symbol will be substituted. Usage
-   * documentation todo.
+   * A comparator that puts SEQUENCE_ID template links before DB_ACCESSION
+   * links, and otherwise orders by link name + url (not case sensitive). It
+   * expects to compare strings formatted as "Name|URLTemplate" where the
+   * template may include $SEQUENCE_ID$ or $DB_ACCESSION$ or neither.
    */
+  public static final Comparator<String> LINK_COMPARATOR = new Comparator<String>()
+  {
+    @Override
+    public int compare(String link1, String link2)
+    {
+      if (link1 == null || link2 == null)
+      {
+        return 0; // for failsafe only
+      }
+      if (link1.contains(SEQUENCEID_PLACEHOLDER)
+              && link2.contains(ACCESSION_PLACEHOLDER))
+      {
+        return -1;
+      }
+      if (link2.contains(SEQUENCEID_PLACEHOLDER)
+              && link1.contains(ACCESSION_PLACEHOLDER))
+      {
+        return 1;
+      }
+      return String.CASE_INSENSITIVE_ORDER.compare(link1, link2);
+    }
+  };
 
   private static final String EQUALS = "=";
 
@@ -291,7 +327,7 @@ public class UrlLink
                       + rg.stringMatched(s) + "'");
             }
             // try to collate subgroup matches
-            Vector<String> subs = new Vector<String>();
+            Vector<String> subs = new Vector<>();
             // have to loop through submatches, collating them at top level
             // match
             int s = 0; // 1;
@@ -632,4 +668,20 @@ public class UrlLink
       }
     }
   }
+
+  /**
+   * Sorts links (formatted as LinkName|LinkPattern) suitable for display in a
+   * menu
+   * <ul>
+   * <li>SEQUENCE_ID links precede DB_ACCESSION links (i.e. canonical lookup
+   * before cross-references)</li>
+   * <li>otherwise by Link name (case insensitive)</li>
+   * </ul>
+   * 
+   * @param nlinks
+   */
+  public static void sort(List<String> nlinks)
+  {
+    Collections.sort(nlinks, LINK_COMPARATOR);
+  }
 }
index 4af6fde..17f9362 100644 (file)
@@ -652,7 +652,7 @@ public abstract class FeatureRendererModel
     {
       featureOrder = new Hashtable<>();
     }
-    featureOrder.put(type, new Float(position));
+    featureOrder.put(type, Float.valueOf(position));
     return position;
   }
 
@@ -852,7 +852,7 @@ public abstract class FeatureRendererModel
     }
     if (newGroupsVisible)
     {
-      featureGroups.put(group, new Boolean(true));
+      featureGroups.put(group, Boolean.valueOf(true));
       return true;
     }
     return false;
@@ -888,7 +888,7 @@ public abstract class FeatureRendererModel
   @Override
   public void setGroupVisibility(String group, boolean visible)
   {
-    featureGroups.put(group, new Boolean(visible));
+    featureGroups.put(group, Boolean.valueOf(visible));
   }
 
   @Override
@@ -900,7 +900,7 @@ public abstract class FeatureRendererModel
       for (String gst : toset)
       {
         Boolean st = featureGroups.get(gst);
-        featureGroups.put(gst, new Boolean(visible));
+        featureGroups.put(gst, Boolean.valueOf(visible));
         if (st != null)
         {
           rdrw = rdrw || (visible != st.booleanValue());
index 29d4ec7..a28be2c 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.ws;
 
 import jalview.ext.ensembl.EnsemblGene;
-import jalview.ext.ensembl.EnsemblGenomes;
 import jalview.ws.dbsources.EmblCdsSource;
 import jalview.ws.dbsources.EmblSource;
 import jalview.ws.dbsources.Pdb;
@@ -49,7 +48,7 @@ public class SequenceFetcher extends ASequenceFetcher
   public SequenceFetcher()
   {
     addDBRefSourceImpl(EnsemblGene.class);
-    addDBRefSourceImpl(EnsemblGenomes.class);
+    // addDBRefSourceImpl(EnsemblGenomes.class);
     addDBRefSourceImpl(EmblSource.class);
     addDBRefSourceImpl(EmblCdsSource.class);
     addDBRefSourceImpl(Uniprot.class);
index 80c9ce9..f9e597f 100644 (file)
@@ -237,7 +237,7 @@ public class RNAalifoldClient extends JabawsCalcWorker
         // The Score objects contain a set of size one containing the range and
         // an ArrayList<float> of size one containing the probabilty
         basePairs.put(score.getRanges().first(),
-                new Float(score.getScores().get(0)));
+                Float.valueOf(score.getScores().get(0)));
       }
 
       for (int i = 0, ri = 0, iEnd = struct.length(); i < iEnd; i++, ri++)
index 2f3c298..e092192 100644 (file)
@@ -38,7 +38,7 @@ import compbio.data.msa.SequenceAnnotation;
 import compbio.metadata.PresetManager;
 import compbio.metadata.RunnerConfig;
 
-public class Jws2Instance
+public class Jws2Instance implements AutoCloseable
 {
   public String hosturl;
 
@@ -164,7 +164,7 @@ public class Jws2Instance
   }
 
   @Override
-  protected void finalize() throws Throwable
+  public void close()
   {
     if (service != null)
     {
@@ -176,7 +176,7 @@ public class Jws2Instance
         // ignore
       }
     }
-    super.finalize();
+    // super.finalize();
   }
 
   public ParamDatastoreI getParamStore()
index 4d5a2aa..5bfe3b5 100644 (file)
@@ -50,7 +50,7 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
  * 
  */
 
-public class HttpResultSet extends FileParse
+public class HttpResultSet extends FileParse implements AutoCloseable
 {
 
   private HttpRequestBase cachedRequest;
@@ -89,7 +89,7 @@ public class HttpResultSet extends FileParse
    */
   public List<DataProvider> createResultDataProviders()
   {
-    List<DataProvider> dp = new ArrayList<DataProvider>();
+    List<DataProvider> dp = new ArrayList<>();
     for (JvDataType type : restJob.rsd.getResultDataTypes())
     {
       dp.add(new SimpleDataProvider(type, this, null));
@@ -106,7 +106,7 @@ public class HttpResultSet extends FileParse
    */
   public Object[] parseResultSet() throws Exception, Error
   {
-    List<DataProvider> dp = new ArrayList<DataProvider>();
+    List<DataProvider> dp = new ArrayList<>();
     Object[] results = null;
 
     if (en == null)
@@ -200,7 +200,7 @@ public class HttpResultSet extends FileParse
   }
 
   @Override
-  protected void finalize() throws Throwable
+  public void close()
   {
     dataIn = null;
     cachedRequest = null;
@@ -215,7 +215,8 @@ public class HttpResultSet extends FileParse
     } catch (Error ex)
     {
     }
-    super.finalize();
+    // no finalize for FileParse
+    // super.close();
   }
 
   /**
diff --git a/src/jalview/xml/binding/jalview/JalviewModelType.java b/src/jalview/xml/binding/jalview/JalviewModelType.java
new file mode 100644 (file)
index 0000000..f084bc3
--- /dev/null
@@ -0,0 +1,5090 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+// Any modifications to this file will be lost upon recompilation of the source schema. 
+// Generated on: 2018.09.18 at 01:33:02 PM BST 
+//
+
+
+package jalview.xml.binding.jalview;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+
+/**
+ * <p>Java class for JalviewModelType complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType name="JalviewModelType">
+ *   &lt;complexContent>
+ *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       &lt;sequence>
+ *         &lt;element name="creationDate" type="{http://www.w3.org/2001/XMLSchema}dateTime"/>
+ *         &lt;element name="version" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *         &lt;element name="vamsasModel" type="{www.vamsas.ac.uk/jalview/version2}VAMSAS"/>
+ *         &lt;sequence>
+ *           &lt;element name="JSeq" maxOccurs="unbounded" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence>
+ *                     &lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/>
+ *                     &lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;extension base="{www.jalview.org}pdbentry">
+ *                             &lt;sequence>
+ *                               &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
+ *                                 &lt;complexType>
+ *                                   &lt;simpleContent>
+ *                                     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+ *                                       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+ *                                       &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                                       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                                       &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                                       &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                                       &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                                       &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                                     &lt;/extension>
+ *                                   &lt;/simpleContent>
+ *                                 &lt;/complexType>
+ *                               &lt;/element>
+ *                             &lt;/sequence>
+ *                           &lt;/extension>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                     &lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/>
+ *                     &lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                             &lt;sequence>
+ *                               &lt;element name="secondaryStructure" maxOccurs="unbounded">
+ *                                 &lt;complexType>
+ *                                   &lt;complexContent>
+ *                                     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                                       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                                       &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                                       &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                                       &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                                     &lt;/restriction>
+ *                                   &lt;/complexContent>
+ *                                 &lt;/complexType>
+ *                               &lt;/element>
+ *                             &lt;/sequence>
+ *                             &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+ *                             &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                             &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                             &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                             &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                           &lt;/restriction>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                   &lt;/sequence>
+ *                   &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *           &lt;element name="JGroup" maxOccurs="unbounded" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence>
+ *                     &lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/>
+ *                     &lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
+ *                   &lt;/sequence>
+ *                   &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *           &lt;element name="Viewport" maxOccurs="unbounded" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence>
+ *                     &lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
+ *                     &lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                             &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                             &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                           &lt;/restriction>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                     &lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
+ *                             &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                             &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                             &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                           &lt;/extension>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                   &lt;/sequence>
+ *                   &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+ *                   &lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+ *                   &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+ *                   &lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ *                   &lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *           &lt;element name="UserColours" maxOccurs="unbounded" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence>
+ *                     &lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/>
+ *                   &lt;/sequence>
+ *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *           &lt;element name="tree" maxOccurs="unbounded" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence minOccurs="0">
+ *                     &lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *                     &lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *                   &lt;/sequence>
+ *                   &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+ *                   &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                   &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                   &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
+ *                   &lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *           &lt;element name="FeatureSettings" minOccurs="0">
+ *             &lt;complexType>
+ *               &lt;complexContent>
+ *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                   &lt;sequence>
+ *                     &lt;element name="setting" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                             &lt;sequence>
+ *                               &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
+ *                               &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
+ *                             &lt;/sequence>
+ *                             &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                             &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                             &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                             &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
+ *                             &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                             &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
+ *                             &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
+ *                             &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                             &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
+ *                             &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
+ *                             &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                             &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                           &lt;/restriction>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                     &lt;element name="group" maxOccurs="unbounded" minOccurs="0">
+ *                       &lt;complexType>
+ *                         &lt;complexContent>
+ *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                             &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                             &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+ *                           &lt;/restriction>
+ *                         &lt;/complexContent>
+ *                       &lt;/complexType>
+ *                     &lt;/element>
+ *                   &lt;/sequence>
+ *                 &lt;/restriction>
+ *               &lt;/complexContent>
+ *             &lt;/complexType>
+ *           &lt;/element>
+ *         &lt;/sequence>
+ *       &lt;/sequence>
+ *     &lt;/restriction>
+ *   &lt;/complexContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "JalviewModelType", namespace = "www.jalview.org", propOrder = {
+    "creationDate",
+    "version",
+    "vamsasModel",
+    "jSeq",
+    "jGroup",
+    "viewport",
+    "userColours",
+    "tree",
+    "featureSettings"
+})
+public class JalviewModelType {
+
+    @XmlElement(required = true)
+    @XmlSchemaType(name = "dateTime")
+    protected XMLGregorianCalendar creationDate;
+    @XmlElement(required = true)
+    protected String version;
+    @XmlElement(required = true)
+    protected VAMSAS vamsasModel;
+    @XmlElement(name = "JSeq")
+    protected List<JalviewModelType.JSeq> jSeq;
+    @XmlElement(name = "JGroup")
+    protected List<JalviewModelType.JGroup> jGroup;
+    @XmlElement(name = "Viewport")
+    protected List<JalviewModelType.Viewport> viewport;
+    @XmlElement(name = "UserColours")
+    protected List<JalviewModelType.UserColours> userColours;
+    protected List<JalviewModelType.Tree> tree;
+    @XmlElement(name = "FeatureSettings")
+    protected JalviewModelType.FeatureSettings featureSettings;
+
+    /**
+     * Gets the value of the creationDate property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link XMLGregorianCalendar }
+     *     
+     */
+    public XMLGregorianCalendar getCreationDate() {
+        return creationDate;
+    }
+
+    /**
+     * Sets the value of the creationDate property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link XMLGregorianCalendar }
+     *     
+     */
+    public void setCreationDate(XMLGregorianCalendar value) {
+        this.creationDate = value;
+    }
+
+    /**
+     * Gets the value of the version property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     *     
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the value of the version property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     *     
+     */
+    public void setVersion(String value) {
+        this.version = value;
+    }
+
+    /**
+     * Gets the value of the vamsasModel property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link VAMSAS }
+     *     
+     */
+    public VAMSAS getVamsasModel() {
+        return vamsasModel;
+    }
+
+    /**
+     * Sets the value of the vamsasModel property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link VAMSAS }
+     *     
+     */
+    public void setVamsasModel(VAMSAS value) {
+        this.vamsasModel = value;
+    }
+
+    /**
+     * Gets the value of the jSeq property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the jSeq property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getJSeq().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link JalviewModelType.JSeq }
+     * 
+     * 
+     */
+    public List<JalviewModelType.JSeq> getJSeq() {
+        if (jSeq == null) {
+            jSeq = new ArrayList<JalviewModelType.JSeq>();
+        }
+        return this.jSeq;
+    }
+
+    /**
+     * Gets the value of the jGroup property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the jGroup property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getJGroup().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link JalviewModelType.JGroup }
+     * 
+     * 
+     */
+    public List<JalviewModelType.JGroup> getJGroup() {
+        if (jGroup == null) {
+            jGroup = new ArrayList<JalviewModelType.JGroup>();
+        }
+        return this.jGroup;
+    }
+
+    /**
+     * Gets the value of the viewport property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the viewport property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getViewport().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link JalviewModelType.Viewport }
+     * 
+     * 
+     */
+    public List<JalviewModelType.Viewport> getViewport() {
+        if (viewport == null) {
+            viewport = new ArrayList<JalviewModelType.Viewport>();
+        }
+        return this.viewport;
+    }
+
+    /**
+     * Gets the value of the userColours property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the userColours property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getUserColours().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link JalviewModelType.UserColours }
+     * 
+     * 
+     */
+    public List<JalviewModelType.UserColours> getUserColours() {
+        if (userColours == null) {
+            userColours = new ArrayList<JalviewModelType.UserColours>();
+        }
+        return this.userColours;
+    }
+
+    /**
+     * Gets the value of the tree property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the tree property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getTree().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link JalviewModelType.Tree }
+     * 
+     * 
+     */
+    public List<JalviewModelType.Tree> getTree() {
+        if (tree == null) {
+            tree = new ArrayList<JalviewModelType.Tree>();
+        }
+        return this.tree;
+    }
+
+    /**
+     * Gets the value of the featureSettings property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link JalviewModelType.FeatureSettings }
+     *     
+     */
+    public JalviewModelType.FeatureSettings getFeatureSettings() {
+        return featureSettings;
+    }
+
+    /**
+     * Sets the value of the featureSettings property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link JalviewModelType.FeatureSettings }
+     *     
+     */
+    public void setFeatureSettings(JalviewModelType.FeatureSettings value) {
+        this.featureSettings = value;
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence>
+     *         &lt;element name="setting" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                 &lt;sequence>
+     *                   &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
+     *                   &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
+     *                 &lt;/sequence>
+     *                 &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *                 &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
+     *                 &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
+     *                 &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
+     *                 &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
+     *                 &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
+     *                 &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *                 &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *               &lt;/restriction>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *         &lt;element name="group" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                 &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *               &lt;/restriction>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *       &lt;/sequence>
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "setting",
+        "group"
+    })
+    public static class FeatureSettings {
+
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.FeatureSettings.Setting> setting;
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.FeatureSettings.Group> group;
+
+        /**
+         * Gets the value of the setting property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the setting property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getSetting().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.FeatureSettings.Setting }
+         * 
+         * 
+         */
+        public List<JalviewModelType.FeatureSettings.Setting> getSetting() {
+            if (setting == null) {
+                setting = new ArrayList<JalviewModelType.FeatureSettings.Setting>();
+            }
+            return this.setting;
+        }
+
+        /**
+         * Gets the value of the group property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the group property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getGroup().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.FeatureSettings.Group }
+         * 
+         * 
+         */
+        public List<JalviewModelType.FeatureSettings.Group> getGroup() {
+            if (group == null) {
+                group = new ArrayList<JalviewModelType.FeatureSettings.Group>();
+            }
+            return this.group;
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *       &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *     &lt;/restriction>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "")
+        public static class Group {
+
+            @XmlAttribute(name = "name", required = true)
+            protected String name;
+            @XmlAttribute(name = "display", required = true)
+            protected boolean display;
+
+            /**
+             * Gets the value of the name property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link String }
+             *     
+             */
+            public String getName() {
+                return name;
+            }
+
+            /**
+             * Sets the value of the name property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link String }
+             *     
+             */
+            public void setName(String value) {
+                this.name = value;
+            }
+
+            /**
+             * Gets the value of the display property.
+             * 
+             */
+            public boolean isDisplay() {
+                return display;
+            }
+
+            /**
+             * Sets the value of the display property.
+             * 
+             */
+            public void setDisplay(boolean value) {
+                this.display = value;
+            }
+
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *       &lt;sequence>
+         *         &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
+         *         &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
+         *       &lt;/sequence>
+         *       &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *       &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
+         *       &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
+         *       &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
+         *       &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
+         *       &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
+         *       &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *       &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *     &lt;/restriction>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "", propOrder = {
+            "attributeName",
+            "matcherSet"
+        })
+        public static class Setting {
+
+            @XmlElement(namespace = "www.jalview.org")
+            protected List<String> attributeName;
+            @XmlElement(namespace = "www.jalview.org")
+            protected FeatureMatcherSet matcherSet;
+            @XmlAttribute(name = "type", required = true)
+            protected String type;
+            @XmlAttribute(name = "colour", required = true)
+            protected int colour;
+            @XmlAttribute(name = "display", required = true)
+            protected boolean display;
+            @XmlAttribute(name = "order")
+            protected Float order;
+            @XmlAttribute(name = "mincolour")
+            protected Integer mincolour;
+            @XmlAttribute(name = "noValueColour")
+            protected NoValueColour noValueColour;
+            @XmlAttribute(name = "threshold")
+            protected Float threshold;
+            @XmlAttribute(name = "threshstate")
+            protected Integer threshstate;
+            @XmlAttribute(name = "max")
+            protected Float max;
+            @XmlAttribute(name = "min")
+            protected Float min;
+            @XmlAttribute(name = "colourByLabel")
+            protected Boolean colourByLabel;
+            @XmlAttribute(name = "autoScale")
+            protected Boolean autoScale;
+
+            /**
+             * Gets the value of the attributeName property.
+             * 
+             * <p>
+             * This accessor method returns a reference to the live list,
+             * not a snapshot. Therefore any modification you make to the
+             * returned list will be present inside the JAXB object.
+             * This is why there is not a <CODE>set</CODE> method for the attributeName property.
+             * 
+             * <p>
+             * For example, to add a new item, do as follows:
+             * <pre>
+             *    getAttributeName().add(newItem);
+             * </pre>
+             * 
+             * 
+             * <p>
+             * Objects of the following type(s) are allowed in the list
+             * {@link String }
+             * 
+             * 
+             */
+            public List<String> getAttributeName() {
+                if (attributeName == null) {
+                    attributeName = new ArrayList<String>();
+                }
+                return this.attributeName;
+            }
+
+            /**
+             * Gets the value of the matcherSet property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link FeatureMatcherSet }
+             *     
+             */
+            public FeatureMatcherSet getMatcherSet() {
+                return matcherSet;
+            }
+
+            /**
+             * Sets the value of the matcherSet property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link FeatureMatcherSet }
+             *     
+             */
+            public void setMatcherSet(FeatureMatcherSet value) {
+                this.matcherSet = value;
+            }
+
+            /**
+             * Gets the value of the type property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link String }
+             *     
+             */
+            public String getType() {
+                return type;
+            }
+
+            /**
+             * Sets the value of the type property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link String }
+             *     
+             */
+            public void setType(String value) {
+                this.type = value;
+            }
+
+            /**
+             * Gets the value of the colour property.
+             * 
+             */
+            public int getColour() {
+                return colour;
+            }
+
+            /**
+             * Sets the value of the colour property.
+             * 
+             */
+            public void setColour(int value) {
+                this.colour = value;
+            }
+
+            /**
+             * Gets the value of the display property.
+             * 
+             */
+            public boolean isDisplay() {
+                return display;
+            }
+
+            /**
+             * Sets the value of the display property.
+             * 
+             */
+            public void setDisplay(boolean value) {
+                this.display = value;
+            }
+
+            /**
+             * Gets the value of the order property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Float }
+             *     
+             */
+            public Float getOrder() {
+                return order;
+            }
+
+            /**
+             * Sets the value of the order property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Float }
+             *     
+             */
+            public void setOrder(Float value) {
+                this.order = value;
+            }
+
+            /**
+             * Gets the value of the mincolour property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getMincolour() {
+                return mincolour;
+            }
+
+            /**
+             * Sets the value of the mincolour property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setMincolour(Integer value) {
+                this.mincolour = value;
+            }
+
+            /**
+             * Gets the value of the noValueColour property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link NoValueColour }
+             *     
+             */
+            public NoValueColour getNoValueColour() {
+                if (noValueColour == null) {
+                    return NoValueColour.MIN;
+                } else {
+                    return noValueColour;
+                }
+            }
+
+            /**
+             * Sets the value of the noValueColour property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link NoValueColour }
+             *     
+             */
+            public void setNoValueColour(NoValueColour value) {
+                this.noValueColour = value;
+            }
+
+            /**
+             * Gets the value of the threshold property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Float }
+             *     
+             */
+            public Float getThreshold() {
+                return threshold;
+            }
+
+            /**
+             * Sets the value of the threshold property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Float }
+             *     
+             */
+            public void setThreshold(Float value) {
+                this.threshold = value;
+            }
+
+            /**
+             * Gets the value of the threshstate property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getThreshstate() {
+                return threshstate;
+            }
+
+            /**
+             * Sets the value of the threshstate property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setThreshstate(Integer value) {
+                this.threshstate = value;
+            }
+
+            /**
+             * Gets the value of the max property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Float }
+             *     
+             */
+            public Float getMax() {
+                return max;
+            }
+
+            /**
+             * Sets the value of the max property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Float }
+             *     
+             */
+            public void setMax(Float value) {
+                this.max = value;
+            }
+
+            /**
+             * Gets the value of the min property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Float }
+             *     
+             */
+            public Float getMin() {
+                return min;
+            }
+
+            /**
+             * Sets the value of the min property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Float }
+             *     
+             */
+            public void setMin(Float value) {
+                this.min = value;
+            }
+
+            /**
+             * Gets the value of the colourByLabel property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Boolean }
+             *     
+             */
+            public Boolean isColourByLabel() {
+                return colourByLabel;
+            }
+
+            /**
+             * Sets the value of the colourByLabel property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Boolean }
+             *     
+             */
+            public void setColourByLabel(Boolean value) {
+                this.colourByLabel = value;
+            }
+
+            /**
+             * Gets the value of the autoScale property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Boolean }
+             *     
+             */
+            public Boolean isAutoScale() {
+                return autoScale;
+            }
+
+            /**
+             * Sets the value of the autoScale property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Boolean }
+             *     
+             */
+            public void setAutoScale(Boolean value) {
+                this.autoScale = value;
+            }
+
+        }
+
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence>
+     *         &lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/>
+     *         &lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
+     *       &lt;/sequence>
+     *       &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "seq",
+        "annotationColours"
+    })
+    public static class JGroup {
+
+        @XmlElement(namespace = "www.jalview.org", required = true)
+        protected List<String> seq;
+        @XmlElement(namespace = "www.jalview.org")
+        protected AnnotationColourScheme annotationColours;
+        @XmlAttribute(name = "start")
+        protected Integer start;
+        @XmlAttribute(name = "end")
+        protected Integer end;
+        @XmlAttribute(name = "name")
+        protected String name;
+        @XmlAttribute(name = "colour")
+        protected String colour;
+        @XmlAttribute(name = "consThreshold")
+        protected Integer consThreshold;
+        @XmlAttribute(name = "pidThreshold")
+        protected Integer pidThreshold;
+        @XmlAttribute(name = "outlineColour")
+        protected Integer outlineColour;
+        @XmlAttribute(name = "displayBoxes")
+        protected Boolean displayBoxes;
+        @XmlAttribute(name = "displayText")
+        protected Boolean displayText;
+        @XmlAttribute(name = "colourText")
+        protected Boolean colourText;
+        @XmlAttribute(name = "textCol1")
+        protected Integer textCol1;
+        @XmlAttribute(name = "textCol2")
+        protected Integer textCol2;
+        @XmlAttribute(name = "textColThreshold")
+        protected Integer textColThreshold;
+        @XmlAttribute(name = "showUnconserved")
+        protected Boolean showUnconserved;
+        @XmlAttribute(name = "ignoreGapsinConsensus")
+        protected Boolean ignoreGapsinConsensus;
+        @XmlAttribute(name = "showConsensusHistogram")
+        protected Boolean showConsensusHistogram;
+        @XmlAttribute(name = "showSequenceLogo")
+        protected Boolean showSequenceLogo;
+        @XmlAttribute(name = "normaliseSequenceLogo")
+        protected Boolean normaliseSequenceLogo;
+        @XmlAttribute(name = "id")
+        protected String id;
+
+        /**
+         * Gets the value of the seq property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the seq property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getSeq().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link String }
+         * 
+         * 
+         */
+        public List<String> getSeq() {
+            if (seq == null) {
+                seq = new ArrayList<String>();
+            }
+            return this.seq;
+        }
+
+        /**
+         * Gets the value of the annotationColours property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link AnnotationColourScheme }
+         *     
+         */
+        public AnnotationColourScheme getAnnotationColours() {
+            return annotationColours;
+        }
+
+        /**
+         * Sets the value of the annotationColours property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link AnnotationColourScheme }
+         *     
+         */
+        public void setAnnotationColours(AnnotationColourScheme value) {
+            this.annotationColours = value;
+        }
+
+        /**
+         * Gets the value of the start property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getStart() {
+            return start;
+        }
+
+        /**
+         * Sets the value of the start property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setStart(Integer value) {
+            this.start = value;
+        }
+
+        /**
+         * Gets the value of the end property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getEnd() {
+            return end;
+        }
+
+        /**
+         * Sets the value of the end property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setEnd(Integer value) {
+            this.end = value;
+        }
+
+        /**
+         * Gets the value of the name property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the value of the name property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setName(String value) {
+            this.name = value;
+        }
+
+        /**
+         * Gets the value of the colour property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getColour() {
+            return colour;
+        }
+
+        /**
+         * Sets the value of the colour property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setColour(String value) {
+            this.colour = value;
+        }
+
+        /**
+         * Gets the value of the consThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getConsThreshold() {
+            return consThreshold;
+        }
+
+        /**
+         * Sets the value of the consThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setConsThreshold(Integer value) {
+            this.consThreshold = value;
+        }
+
+        /**
+         * Gets the value of the pidThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getPidThreshold() {
+            return pidThreshold;
+        }
+
+        /**
+         * Sets the value of the pidThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setPidThreshold(Integer value) {
+            this.pidThreshold = value;
+        }
+
+        /**
+         * Gets the value of the outlineColour property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getOutlineColour() {
+            return outlineColour;
+        }
+
+        /**
+         * Sets the value of the outlineColour property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setOutlineColour(Integer value) {
+            this.outlineColour = value;
+        }
+
+        /**
+         * Gets the value of the displayBoxes property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isDisplayBoxes() {
+            return displayBoxes;
+        }
+
+        /**
+         * Sets the value of the displayBoxes property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setDisplayBoxes(Boolean value) {
+            this.displayBoxes = value;
+        }
+
+        /**
+         * Gets the value of the displayText property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isDisplayText() {
+            return displayText;
+        }
+
+        /**
+         * Sets the value of the displayText property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setDisplayText(Boolean value) {
+            this.displayText = value;
+        }
+
+        /**
+         * Gets the value of the colourText property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isColourText() {
+            return colourText;
+        }
+
+        /**
+         * Sets the value of the colourText property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setColourText(Boolean value) {
+            this.colourText = value;
+        }
+
+        /**
+         * Gets the value of the textCol1 property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextCol1() {
+            return textCol1;
+        }
+
+        /**
+         * Sets the value of the textCol1 property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextCol1(Integer value) {
+            this.textCol1 = value;
+        }
+
+        /**
+         * Gets the value of the textCol2 property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextCol2() {
+            return textCol2;
+        }
+
+        /**
+         * Sets the value of the textCol2 property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextCol2(Integer value) {
+            this.textCol2 = value;
+        }
+
+        /**
+         * Gets the value of the textColThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextColThreshold() {
+            return textColThreshold;
+        }
+
+        /**
+         * Sets the value of the textColThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextColThreshold(Integer value) {
+            this.textColThreshold = value;
+        }
+
+        /**
+         * Gets the value of the showUnconserved property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowUnconserved() {
+            return showUnconserved;
+        }
+
+        /**
+         * Sets the value of the showUnconserved property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowUnconserved(Boolean value) {
+            this.showUnconserved = value;
+        }
+
+        /**
+         * Gets the value of the ignoreGapsinConsensus property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isIgnoreGapsinConsensus() {
+            if (ignoreGapsinConsensus == null) {
+                return true;
+            } else {
+                return ignoreGapsinConsensus;
+            }
+        }
+
+        /**
+         * Sets the value of the ignoreGapsinConsensus property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setIgnoreGapsinConsensus(Boolean value) {
+            this.ignoreGapsinConsensus = value;
+        }
+
+        /**
+         * Gets the value of the showConsensusHistogram property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowConsensusHistogram() {
+            if (showConsensusHistogram == null) {
+                return true;
+            } else {
+                return showConsensusHistogram;
+            }
+        }
+
+        /**
+         * Sets the value of the showConsensusHistogram property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowConsensusHistogram(Boolean value) {
+            this.showConsensusHistogram = value;
+        }
+
+        /**
+         * Gets the value of the showSequenceLogo property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowSequenceLogo() {
+            if (showSequenceLogo == null) {
+                return false;
+            } else {
+                return showSequenceLogo;
+            }
+        }
+
+        /**
+         * Sets the value of the showSequenceLogo property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowSequenceLogo(Boolean value) {
+            this.showSequenceLogo = value;
+        }
+
+        /**
+         * Gets the value of the normaliseSequenceLogo property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isNormaliseSequenceLogo() {
+            if (normaliseSequenceLogo == null) {
+                return false;
+            } else {
+                return normaliseSequenceLogo;
+            }
+        }
+
+        /**
+         * Sets the value of the normaliseSequenceLogo property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setNormaliseSequenceLogo(Boolean value) {
+            this.normaliseSequenceLogo = value;
+        }
+
+        /**
+         * Gets the value of the id property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getId() {
+            return id;
+        }
+
+        /**
+         * Sets the value of the id property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setId(String value) {
+            this.id = value;
+        }
+
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence>
+     *         &lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/>
+     *         &lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;extension base="{www.jalview.org}pdbentry">
+     *                 &lt;sequence>
+     *                   &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
+     *                     &lt;complexType>
+     *                       &lt;simpleContent>
+     *                         &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+     *                           &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+     *                           &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *                           &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                           &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *                           &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *                           &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *                           &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                         &lt;/extension>
+     *                       &lt;/simpleContent>
+     *                     &lt;/complexType>
+     *                   &lt;/element>
+     *                 &lt;/sequence>
+     *               &lt;/extension>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *         &lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/>
+     *         &lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                 &lt;sequence>
+     *                   &lt;element name="secondaryStructure" maxOccurs="unbounded">
+     *                     &lt;complexType>
+     *                       &lt;complexContent>
+     *                         &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                           &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                           &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                           &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *                           &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                         &lt;/restriction>
+     *                       &lt;/complexContent>
+     *                     &lt;/complexType>
+     *                   &lt;/element>
+     *                 &lt;/sequence>
+     *                 &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+     *                 &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *               &lt;/restriction>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *       &lt;/sequence>
+     *       &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "features",
+        "pdbids",
+        "hiddenSequences",
+        "rnaViewer"
+    })
+    public static class JSeq {
+
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<Feature> features;
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.JSeq.Pdbids> pdbids;
+        @XmlElement(namespace = "www.jalview.org", type = Integer.class)
+        protected List<Integer> hiddenSequences;
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.JSeq.RnaViewer> rnaViewer;
+        @XmlAttribute(name = "colour")
+        protected Integer colour;
+        @XmlAttribute(name = "start", required = true)
+        protected int start;
+        @XmlAttribute(name = "end", required = true)
+        protected int end;
+        @XmlAttribute(name = "id", required = true)
+        protected String id;
+        @XmlAttribute(name = "hidden")
+        protected Boolean hidden;
+        @XmlAttribute(name = "viewreference")
+        protected Boolean viewreference;
+
+        /**
+         * Gets the value of the features property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the features property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getFeatures().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link Feature }
+         * 
+         * 
+         */
+        public List<Feature> getFeatures() {
+            if (features == null) {
+                features = new ArrayList<Feature>();
+            }
+            return this.features;
+        }
+
+        /**
+         * Gets the value of the pdbids property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the pdbids property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getPdbids().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.JSeq.Pdbids }
+         * 
+         * 
+         */
+        public List<JalviewModelType.JSeq.Pdbids> getPdbids() {
+            if (pdbids == null) {
+                pdbids = new ArrayList<JalviewModelType.JSeq.Pdbids>();
+            }
+            return this.pdbids;
+        }
+
+        /**
+         * Gets the value of the hiddenSequences property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the hiddenSequences property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getHiddenSequences().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link Integer }
+         * 
+         * 
+         */
+        public List<Integer> getHiddenSequences() {
+            if (hiddenSequences == null) {
+                hiddenSequences = new ArrayList<Integer>();
+            }
+            return this.hiddenSequences;
+        }
+
+        /**
+         * Gets the value of the rnaViewer property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the rnaViewer property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getRnaViewer().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.JSeq.RnaViewer }
+         * 
+         * 
+         */
+        public List<JalviewModelType.JSeq.RnaViewer> getRnaViewer() {
+            if (rnaViewer == null) {
+                rnaViewer = new ArrayList<JalviewModelType.JSeq.RnaViewer>();
+            }
+            return this.rnaViewer;
+        }
+
+        /**
+         * Gets the value of the colour property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getColour() {
+            return colour;
+        }
+
+        /**
+         * Sets the value of the colour property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setColour(Integer value) {
+            this.colour = value;
+        }
+
+        /**
+         * Gets the value of the start property.
+         * 
+         */
+        public int getStart() {
+            return start;
+        }
+
+        /**
+         * Sets the value of the start property.
+         * 
+         */
+        public void setStart(int value) {
+            this.start = value;
+        }
+
+        /**
+         * Gets the value of the end property.
+         * 
+         */
+        public int getEnd() {
+            return end;
+        }
+
+        /**
+         * Sets the value of the end property.
+         * 
+         */
+        public void setEnd(int value) {
+            this.end = value;
+        }
+
+        /**
+         * Gets the value of the id property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getId() {
+            return id;
+        }
+
+        /**
+         * Sets the value of the id property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setId(String value) {
+            this.id = value;
+        }
+
+        /**
+         * Gets the value of the hidden property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isHidden() {
+            return hidden;
+        }
+
+        /**
+         * Sets the value of the hidden property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setHidden(Boolean value) {
+            this.hidden = value;
+        }
+
+        /**
+         * Gets the value of the viewreference property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isViewreference() {
+            return viewreference;
+        }
+
+        /**
+         * Sets the value of the viewreference property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setViewreference(Boolean value) {
+            this.viewreference = value;
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;extension base="{www.jalview.org}pdbentry">
+         *       &lt;sequence>
+         *         &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
+         *           &lt;complexType>
+         *             &lt;simpleContent>
+         *               &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+         *                 &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+         *                 &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *                 &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *                 &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+         *                 &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+         *                 &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+         *                 &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *               &lt;/extension>
+         *             &lt;/simpleContent>
+         *           &lt;/complexType>
+         *         &lt;/element>
+         *       &lt;/sequence>
+         *     &lt;/extension>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "", propOrder = {
+            "structureState"
+        })
+        public static class Pdbids
+            extends Pdbentry
+        {
+
+            @XmlElement(namespace = "www.jalview.org")
+            protected List<JalviewModelType.JSeq.Pdbids.StructureState> structureState;
+
+            /**
+             * Gets the value of the structureState property.
+             * 
+             * <p>
+             * This accessor method returns a reference to the live list,
+             * not a snapshot. Therefore any modification you make to the
+             * returned list will be present inside the JAXB object.
+             * This is why there is not a <CODE>set</CODE> method for the structureState property.
+             * 
+             * <p>
+             * For example, to add a new item, do as follows:
+             * <pre>
+             *    getStructureState().add(newItem);
+             * </pre>
+             * 
+             * 
+             * <p>
+             * Objects of the following type(s) are allowed in the list
+             * {@link JalviewModelType.JSeq.Pdbids.StructureState }
+             * 
+             * 
+             */
+            public List<JalviewModelType.JSeq.Pdbids.StructureState> getStructureState() {
+                if (structureState == null) {
+                    structureState = new ArrayList<JalviewModelType.JSeq.Pdbids.StructureState>();
+                }
+                return this.structureState;
+            }
+
+
+            /**
+             * <p>Java class for anonymous complex type.
+             * 
+             * <p>The following schema fragment specifies the expected content contained within this class.
+             * 
+             * <pre>
+             * &lt;complexType>
+             *   &lt;simpleContent>
+             *     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
+             *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+             *       &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+             *       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+             *       &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+             *       &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+             *       &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+             *       &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
+             *     &lt;/extension>
+             *   &lt;/simpleContent>
+             * &lt;/complexType>
+             * </pre>
+             * 
+             * 
+             */
+            @XmlAccessorType(XmlAccessType.FIELD)
+            @XmlType(name = "", propOrder = {
+                "value"
+            })
+            public static class StructureState {
+
+                @XmlValue
+                protected String value;
+                @XmlAttribute(name = "visible")
+                protected Boolean visible;
+                @XmlAttribute(name = "viewId")
+                protected String viewId;
+                @XmlAttribute(name = "alignwithAlignPanel")
+                protected Boolean alignwithAlignPanel;
+                @XmlAttribute(name = "colourwithAlignPanel")
+                protected Boolean colourwithAlignPanel;
+                @XmlAttribute(name = "colourByJmol")
+                protected Boolean colourByJmol;
+                @XmlAttribute(name = "type")
+                protected String type;
+                @XmlAttribute(name = "width")
+                protected Integer width;
+                @XmlAttribute(name = "height")
+                protected Integer height;
+                @XmlAttribute(name = "xpos")
+                protected Integer xpos;
+                @XmlAttribute(name = "ypos")
+                protected Integer ypos;
+
+                /**
+                 * Gets the value of the value property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getValue() {
+                    return value;
+                }
+
+                /**
+                 * Sets the value of the value property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setValue(String value) {
+                    this.value = value;
+                }
+
+                /**
+                 * Gets the value of the visible property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public Boolean isVisible() {
+                    return visible;
+                }
+
+                /**
+                 * Sets the value of the visible property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public void setVisible(Boolean value) {
+                    this.visible = value;
+                }
+
+                /**
+                 * Gets the value of the viewId property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getViewId() {
+                    return viewId;
+                }
+
+                /**
+                 * Sets the value of the viewId property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setViewId(String value) {
+                    this.viewId = value;
+                }
+
+                /**
+                 * Gets the value of the alignwithAlignPanel property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public boolean isAlignwithAlignPanel() {
+                    if (alignwithAlignPanel == null) {
+                        return true;
+                    } else {
+                        return alignwithAlignPanel;
+                    }
+                }
+
+                /**
+                 * Sets the value of the alignwithAlignPanel property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public void setAlignwithAlignPanel(Boolean value) {
+                    this.alignwithAlignPanel = value;
+                }
+
+                /**
+                 * Gets the value of the colourwithAlignPanel property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public boolean isColourwithAlignPanel() {
+                    if (colourwithAlignPanel == null) {
+                        return false;
+                    } else {
+                        return colourwithAlignPanel;
+                    }
+                }
+
+                /**
+                 * Sets the value of the colourwithAlignPanel property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public void setColourwithAlignPanel(Boolean value) {
+                    this.colourwithAlignPanel = value;
+                }
+
+                /**
+                 * Gets the value of the colourByJmol property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public boolean isColourByJmol() {
+                    if (colourByJmol == null) {
+                        return true;
+                    } else {
+                        return colourByJmol;
+                    }
+                }
+
+                /**
+                 * Sets the value of the colourByJmol property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public void setColourByJmol(Boolean value) {
+                    this.colourByJmol = value;
+                }
+
+                /**
+                 * Gets the value of the type property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getType() {
+                    return type;
+                }
+
+                /**
+                 * Sets the value of the type property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setType(String value) {
+                    this.type = value;
+                }
+
+                /**
+                 * Gets the value of the width property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public Integer getWidth() {
+                    return width;
+                }
+
+                /**
+                 * Sets the value of the width property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public void setWidth(Integer value) {
+                    this.width = value;
+                }
+
+                /**
+                 * Gets the value of the height property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public Integer getHeight() {
+                    return height;
+                }
+
+                /**
+                 * Sets the value of the height property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public void setHeight(Integer value) {
+                    this.height = value;
+                }
+
+                /**
+                 * Gets the value of the xpos property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public Integer getXpos() {
+                    return xpos;
+                }
+
+                /**
+                 * Sets the value of the xpos property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public void setXpos(Integer value) {
+                    this.xpos = value;
+                }
+
+                /**
+                 * Gets the value of the ypos property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public Integer getYpos() {
+                    return ypos;
+                }
+
+                /**
+                 * Sets the value of the ypos property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Integer }
+                 *     
+                 */
+                public void setYpos(Integer value) {
+                    this.ypos = value;
+                }
+
+            }
+
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *       &lt;sequence>
+         *         &lt;element name="secondaryStructure" maxOccurs="unbounded">
+         *           &lt;complexType>
+         *             &lt;complexContent>
+         *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *                 &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *                 &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *                 &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *                 &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *               &lt;/restriction>
+         *             &lt;/complexContent>
+         *           &lt;/complexType>
+         *         &lt;/element>
+         *       &lt;/sequence>
+         *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+         *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *     &lt;/restriction>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "", propOrder = {
+            "secondaryStructure"
+        })
+        public static class RnaViewer {
+
+            @XmlElement(namespace = "www.jalview.org", required = true)
+            protected List<JalviewModelType.JSeq.RnaViewer.SecondaryStructure> secondaryStructure;
+            @XmlAttribute(name = "title")
+            protected String title;
+            @XmlAttribute(name = "viewId")
+            protected String viewId;
+            @XmlAttribute(name = "dividerLocation")
+            protected Integer dividerLocation;
+            @XmlAttribute(name = "selectedRna")
+            protected Integer selectedRna;
+            @XmlAttribute(name = "width")
+            protected Integer width;
+            @XmlAttribute(name = "height")
+            protected Integer height;
+            @XmlAttribute(name = "xpos")
+            protected Integer xpos;
+            @XmlAttribute(name = "ypos")
+            protected Integer ypos;
+
+            /**
+             * Gets the value of the secondaryStructure property.
+             * 
+             * <p>
+             * This accessor method returns a reference to the live list,
+             * not a snapshot. Therefore any modification you make to the
+             * returned list will be present inside the JAXB object.
+             * This is why there is not a <CODE>set</CODE> method for the secondaryStructure property.
+             * 
+             * <p>
+             * For example, to add a new item, do as follows:
+             * <pre>
+             *    getSecondaryStructure().add(newItem);
+             * </pre>
+             * 
+             * 
+             * <p>
+             * Objects of the following type(s) are allowed in the list
+             * {@link JalviewModelType.JSeq.RnaViewer.SecondaryStructure }
+             * 
+             * 
+             */
+            public List<JalviewModelType.JSeq.RnaViewer.SecondaryStructure> getSecondaryStructure() {
+                if (secondaryStructure == null) {
+                    secondaryStructure = new ArrayList<JalviewModelType.JSeq.RnaViewer.SecondaryStructure>();
+                }
+                return this.secondaryStructure;
+            }
+
+            /**
+             * Gets the value of the title property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link String }
+             *     
+             */
+            public String getTitle() {
+                return title;
+            }
+
+            /**
+             * Sets the value of the title property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link String }
+             *     
+             */
+            public void setTitle(String value) {
+                this.title = value;
+            }
+
+            /**
+             * Gets the value of the viewId property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link String }
+             *     
+             */
+            public String getViewId() {
+                return viewId;
+            }
+
+            /**
+             * Sets the value of the viewId property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link String }
+             *     
+             */
+            public void setViewId(String value) {
+                this.viewId = value;
+            }
+
+            /**
+             * Gets the value of the dividerLocation property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getDividerLocation() {
+                return dividerLocation;
+            }
+
+            /**
+             * Sets the value of the dividerLocation property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setDividerLocation(Integer value) {
+                this.dividerLocation = value;
+            }
+
+            /**
+             * Gets the value of the selectedRna property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getSelectedRna() {
+                return selectedRna;
+            }
+
+            /**
+             * Sets the value of the selectedRna property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setSelectedRna(Integer value) {
+                this.selectedRna = value;
+            }
+
+            /**
+             * Gets the value of the width property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getWidth() {
+                return width;
+            }
+
+            /**
+             * Sets the value of the width property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setWidth(Integer value) {
+                this.width = value;
+            }
+
+            /**
+             * Gets the value of the height property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getHeight() {
+                return height;
+            }
+
+            /**
+             * Sets the value of the height property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setHeight(Integer value) {
+                this.height = value;
+            }
+
+            /**
+             * Gets the value of the xpos property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getXpos() {
+                return xpos;
+            }
+
+            /**
+             * Sets the value of the xpos property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setXpos(Integer value) {
+                this.xpos = value;
+            }
+
+            /**
+             * Gets the value of the ypos property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getYpos() {
+                return ypos;
+            }
+
+            /**
+             * Sets the value of the ypos property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setYpos(Integer value) {
+                this.ypos = value;
+            }
+
+
+            /**
+             * <p>Java class for anonymous complex type.
+             * 
+             * <p>The following schema fragment specifies the expected content contained within this class.
+             * 
+             * <pre>
+             * &lt;complexType>
+             *   &lt;complexContent>
+             *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+             *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+             *       &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+             *       &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+             *       &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
+             *     &lt;/restriction>
+             *   &lt;/complexContent>
+             * &lt;/complexType>
+             * </pre>
+             * 
+             * 
+             */
+            @XmlAccessorType(XmlAccessType.FIELD)
+            @XmlType(name = "")
+            public static class SecondaryStructure {
+
+                @XmlAttribute(name = "title")
+                protected String title;
+                @XmlAttribute(name = "annotationId", required = true)
+                protected String annotationId;
+                @XmlAttribute(name = "gapped")
+                protected Boolean gapped;
+                @XmlAttribute(name = "viewerState")
+                protected String viewerState;
+
+                /**
+                 * Gets the value of the title property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getTitle() {
+                    return title;
+                }
+
+                /**
+                 * Sets the value of the title property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setTitle(String value) {
+                    this.title = value;
+                }
+
+                /**
+                 * Gets the value of the annotationId property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getAnnotationId() {
+                    return annotationId;
+                }
+
+                /**
+                 * Sets the value of the annotationId property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setAnnotationId(String value) {
+                    this.annotationId = value;
+                }
+
+                /**
+                 * Gets the value of the gapped property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public Boolean isGapped() {
+                    return gapped;
+                }
+
+                /**
+                 * Sets the value of the gapped property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link Boolean }
+                 *     
+                 */
+                public void setGapped(Boolean value) {
+                    this.gapped = value;
+                }
+
+                /**
+                 * Gets the value of the viewerState property.
+                 * 
+                 * @return
+                 *     possible object is
+                 *     {@link String }
+                 *     
+                 */
+                public String getViewerState() {
+                    return viewerState;
+                }
+
+                /**
+                 * Sets the value of the viewerState property.
+                 * 
+                 * @param value
+                 *     allowed object is
+                 *     {@link String }
+                 *     
+                 */
+                public void setViewerState(String value) {
+                    this.viewerState = value;
+                }
+
+            }
+
+        }
+
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence minOccurs="0">
+     *         &lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/>
+     *         &lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/>
+     *       &lt;/sequence>
+     *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+     *       &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
+     *       &lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "title",
+        "newick"
+    })
+    public static class Tree {
+
+        @XmlElement(namespace = "www.jalview.org")
+        protected String title;
+        @XmlElement(namespace = "www.jalview.org")
+        protected String newick;
+        @XmlAttribute(name = "fontName")
+        protected String fontName;
+        @XmlAttribute(name = "fontSize")
+        protected Integer fontSize;
+        @XmlAttribute(name = "fontStyle")
+        protected Integer fontStyle;
+        @XmlAttribute(name = "threshold")
+        protected Float threshold;
+        @XmlAttribute(name = "showBootstrap")
+        protected Boolean showBootstrap;
+        @XmlAttribute(name = "showDistances")
+        protected Boolean showDistances;
+        @XmlAttribute(name = "markUnlinked")
+        protected Boolean markUnlinked;
+        @XmlAttribute(name = "fitToWindow")
+        protected Boolean fitToWindow;
+        @XmlAttribute(name = "currentTree")
+        protected Boolean currentTree;
+        @XmlAttribute(name = "id")
+        @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+        @XmlID
+        @XmlSchemaType(name = "ID")
+        protected String id;
+        @XmlAttribute(name = "width")
+        protected Integer width;
+        @XmlAttribute(name = "height")
+        protected Integer height;
+        @XmlAttribute(name = "xpos")
+        protected Integer xpos;
+        @XmlAttribute(name = "ypos")
+        protected Integer ypos;
+
+        /**
+         * Gets the value of the title property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getTitle() {
+            return title;
+        }
+
+        /**
+         * Sets the value of the title property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setTitle(String value) {
+            this.title = value;
+        }
+
+        /**
+         * Gets the value of the newick property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getNewick() {
+            return newick;
+        }
+
+        /**
+         * Sets the value of the newick property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setNewick(String value) {
+            this.newick = value;
+        }
+
+        /**
+         * Gets the value of the fontName property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getFontName() {
+            return fontName;
+        }
+
+        /**
+         * Sets the value of the fontName property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setFontName(String value) {
+            this.fontName = value;
+        }
+
+        /**
+         * Gets the value of the fontSize property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getFontSize() {
+            return fontSize;
+        }
+
+        /**
+         * Sets the value of the fontSize property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setFontSize(Integer value) {
+            this.fontSize = value;
+        }
+
+        /**
+         * Gets the value of the fontStyle property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getFontStyle() {
+            return fontStyle;
+        }
+
+        /**
+         * Sets the value of the fontStyle property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setFontStyle(Integer value) {
+            this.fontStyle = value;
+        }
+
+        /**
+         * Gets the value of the threshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Float }
+         *     
+         */
+        public Float getThreshold() {
+            return threshold;
+        }
+
+        /**
+         * Sets the value of the threshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Float }
+         *     
+         */
+        public void setThreshold(Float value) {
+            this.threshold = value;
+        }
+
+        /**
+         * Gets the value of the showBootstrap property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowBootstrap() {
+            return showBootstrap;
+        }
+
+        /**
+         * Sets the value of the showBootstrap property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowBootstrap(Boolean value) {
+            this.showBootstrap = value;
+        }
+
+        /**
+         * Gets the value of the showDistances property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowDistances() {
+            return showDistances;
+        }
+
+        /**
+         * Sets the value of the showDistances property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowDistances(Boolean value) {
+            this.showDistances = value;
+        }
+
+        /**
+         * Gets the value of the markUnlinked property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isMarkUnlinked() {
+            return markUnlinked;
+        }
+
+        /**
+         * Sets the value of the markUnlinked property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setMarkUnlinked(Boolean value) {
+            this.markUnlinked = value;
+        }
+
+        /**
+         * Gets the value of the fitToWindow property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isFitToWindow() {
+            return fitToWindow;
+        }
+
+        /**
+         * Sets the value of the fitToWindow property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setFitToWindow(Boolean value) {
+            this.fitToWindow = value;
+        }
+
+        /**
+         * Gets the value of the currentTree property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isCurrentTree() {
+            return currentTree;
+        }
+
+        /**
+         * Sets the value of the currentTree property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setCurrentTree(Boolean value) {
+            this.currentTree = value;
+        }
+
+        /**
+         * Gets the value of the id property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getId() {
+            return id;
+        }
+
+        /**
+         * Sets the value of the id property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setId(String value) {
+            this.id = value;
+        }
+
+        /**
+         * Gets the value of the width property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getWidth() {
+            return width;
+        }
+
+        /**
+         * Sets the value of the width property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setWidth(Integer value) {
+            this.width = value;
+        }
+
+        /**
+         * Gets the value of the height property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getHeight() {
+            return height;
+        }
+
+        /**
+         * Sets the value of the height property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setHeight(Integer value) {
+            this.height = value;
+        }
+
+        /**
+         * Gets the value of the xpos property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getXpos() {
+            return xpos;
+        }
+
+        /**
+         * Sets the value of the xpos property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setXpos(Integer value) {
+            this.xpos = value;
+        }
+
+        /**
+         * Gets the value of the ypos property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getYpos() {
+            return ypos;
+        }
+
+        /**
+         * Sets the value of the ypos property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setYpos(Integer value) {
+            this.ypos = value;
+        }
+
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence>
+     *         &lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/>
+     *       &lt;/sequence>
+     *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "userColourScheme"
+    })
+    public static class UserColours {
+
+        @XmlElement(name = "UserColourScheme", namespace = "www.jalview.org", required = true)
+        protected JalviewUserColours userColourScheme;
+        @XmlAttribute(name = "id")
+        protected String id;
+
+        /**
+         * Gets the value of the userColourScheme property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link JalviewUserColours }
+         *     
+         */
+        public JalviewUserColours getUserColourScheme() {
+            return userColourScheme;
+        }
+
+        /**
+         * Sets the value of the userColourScheme property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link JalviewUserColours }
+         *     
+         */
+        public void setUserColourScheme(JalviewUserColours value) {
+            this.userColourScheme = value;
+        }
+
+        /**
+         * Gets the value of the id property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getId() {
+            return id;
+        }
+
+        /**
+         * Sets the value of the id property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setId(String value) {
+            this.id = value;
+        }
+
+    }
+
+
+    /**
+     * <p>Java class for anonymous complex type.
+     * 
+     * <p>The following schema fragment specifies the expected content contained within this class.
+     * 
+     * <pre>
+     * &lt;complexType>
+     *   &lt;complexContent>
+     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       &lt;sequence>
+     *         &lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
+     *         &lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                 &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *               &lt;/restriction>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *         &lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0">
+     *           &lt;complexType>
+     *             &lt;complexContent>
+     *               &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
+     *                 &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *                 &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *               &lt;/extension>
+     *             &lt;/complexContent>
+     *           &lt;/complexType>
+     *         &lt;/element>
+     *       &lt;/sequence>
+     *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
+     *       &lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+     *       &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
+     *       &lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *       &lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+     *       &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
+     *       &lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *     &lt;/restriction>
+     *   &lt;/complexContent>
+     * &lt;/complexType>
+     * </pre>
+     * 
+     * 
+     */
+    @XmlAccessorType(XmlAccessType.FIELD)
+    @XmlType(name = "", propOrder = {
+        "annotationColours",
+        "hiddenColumns",
+        "calcIdParam"
+    })
+    public static class Viewport {
+
+        @XmlElement(name = "AnnotationColours", namespace = "www.jalview.org")
+        protected AnnotationColourScheme annotationColours;
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.Viewport.HiddenColumns> hiddenColumns;
+        @XmlElement(namespace = "www.jalview.org")
+        protected List<JalviewModelType.Viewport.CalcIdParam> calcIdParam;
+        @XmlAttribute(name = "conservationSelected")
+        protected Boolean conservationSelected;
+        @XmlAttribute(name = "pidSelected")
+        protected Boolean pidSelected;
+        @XmlAttribute(name = "bgColour")
+        protected String bgColour;
+        @XmlAttribute(name = "consThreshold")
+        protected Integer consThreshold;
+        @XmlAttribute(name = "pidThreshold")
+        protected Integer pidThreshold;
+        @XmlAttribute(name = "title")
+        protected String title;
+        @XmlAttribute(name = "showFullId")
+        protected Boolean showFullId;
+        @XmlAttribute(name = "rightAlignIds")
+        protected Boolean rightAlignIds;
+        @XmlAttribute(name = "showText")
+        protected Boolean showText;
+        @XmlAttribute(name = "showColourText")
+        protected Boolean showColourText;
+        @XmlAttribute(name = "showUnconserved")
+        protected Boolean showUnconserved;
+        @XmlAttribute(name = "showBoxes")
+        protected Boolean showBoxes;
+        @XmlAttribute(name = "wrapAlignment")
+        protected Boolean wrapAlignment;
+        @XmlAttribute(name = "renderGaps")
+        protected Boolean renderGaps;
+        @XmlAttribute(name = "showSequenceFeatures")
+        protected Boolean showSequenceFeatures;
+        @XmlAttribute(name = "showNPfeatureTooltip")
+        protected Boolean showNPfeatureTooltip;
+        @XmlAttribute(name = "showDbRefTooltip")
+        protected Boolean showDbRefTooltip;
+        @XmlAttribute(name = "followHighlight")
+        protected Boolean followHighlight;
+        @XmlAttribute(name = "followSelection")
+        protected Boolean followSelection;
+        @XmlAttribute(name = "showAnnotation")
+        protected Boolean showAnnotation;
+        @XmlAttribute(name = "centreColumnLabels")
+        protected Boolean centreColumnLabels;
+        @XmlAttribute(name = "showGroupConservation")
+        protected Boolean showGroupConservation;
+        @XmlAttribute(name = "showGroupConsensus")
+        protected Boolean showGroupConsensus;
+        @XmlAttribute(name = "showConsensusHistogram")
+        protected Boolean showConsensusHistogram;
+        @XmlAttribute(name = "showSequenceLogo")
+        protected Boolean showSequenceLogo;
+        @XmlAttribute(name = "normaliseSequenceLogo")
+        protected Boolean normaliseSequenceLogo;
+        @XmlAttribute(name = "ignoreGapsinConsensus")
+        protected Boolean ignoreGapsinConsensus;
+        @XmlAttribute(name = "startRes")
+        protected Integer startRes;
+        @XmlAttribute(name = "startSeq")
+        protected Integer startSeq;
+        @XmlAttribute(name = "fontName")
+        protected String fontName;
+        @XmlAttribute(name = "fontSize")
+        protected Integer fontSize;
+        @XmlAttribute(name = "fontStyle")
+        protected Integer fontStyle;
+        @XmlAttribute(name = "scaleProteinAsCdna")
+        protected Boolean scaleProteinAsCdna;
+        @XmlAttribute(name = "viewName")
+        protected String viewName;
+        @XmlAttribute(name = "sequenceSetId")
+        protected String sequenceSetId;
+        @XmlAttribute(name = "gatheredViews")
+        protected Boolean gatheredViews;
+        @XmlAttribute(name = "textCol1")
+        protected Integer textCol1;
+        @XmlAttribute(name = "textCol2")
+        protected Integer textCol2;
+        @XmlAttribute(name = "textColThreshold")
+        protected Integer textColThreshold;
+        @XmlAttribute(name = "id")
+        @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+        @XmlID
+        @XmlSchemaType(name = "ID")
+        protected String id;
+        @XmlAttribute(name = "complementId")
+        protected String complementId;
+        @XmlAttribute(name = "width")
+        protected Integer width;
+        @XmlAttribute(name = "height")
+        protected Integer height;
+        @XmlAttribute(name = "xpos")
+        protected Integer xpos;
+        @XmlAttribute(name = "ypos")
+        protected Integer ypos;
+
+        /**
+         * Gets the value of the annotationColours property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link AnnotationColourScheme }
+         *     
+         */
+        public AnnotationColourScheme getAnnotationColours() {
+            return annotationColours;
+        }
+
+        /**
+         * Sets the value of the annotationColours property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link AnnotationColourScheme }
+         *     
+         */
+        public void setAnnotationColours(AnnotationColourScheme value) {
+            this.annotationColours = value;
+        }
+
+        /**
+         * Gets the value of the hiddenColumns property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the hiddenColumns property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getHiddenColumns().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.Viewport.HiddenColumns }
+         * 
+         * 
+         */
+        public List<JalviewModelType.Viewport.HiddenColumns> getHiddenColumns() {
+            if (hiddenColumns == null) {
+                hiddenColumns = new ArrayList<JalviewModelType.Viewport.HiddenColumns>();
+            }
+            return this.hiddenColumns;
+        }
+
+        /**
+         * Gets the value of the calcIdParam property.
+         * 
+         * <p>
+         * This accessor method returns a reference to the live list,
+         * not a snapshot. Therefore any modification you make to the
+         * returned list will be present inside the JAXB object.
+         * This is why there is not a <CODE>set</CODE> method for the calcIdParam property.
+         * 
+         * <p>
+         * For example, to add a new item, do as follows:
+         * <pre>
+         *    getCalcIdParam().add(newItem);
+         * </pre>
+         * 
+         * 
+         * <p>
+         * Objects of the following type(s) are allowed in the list
+         * {@link JalviewModelType.Viewport.CalcIdParam }
+         * 
+         * 
+         */
+        public List<JalviewModelType.Viewport.CalcIdParam> getCalcIdParam() {
+            if (calcIdParam == null) {
+                calcIdParam = new ArrayList<JalviewModelType.Viewport.CalcIdParam>();
+            }
+            return this.calcIdParam;
+        }
+
+        /**
+         * Gets the value of the conservationSelected property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isConservationSelected() {
+            return conservationSelected;
+        }
+
+        /**
+         * Sets the value of the conservationSelected property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setConservationSelected(Boolean value) {
+            this.conservationSelected = value;
+        }
+
+        /**
+         * Gets the value of the pidSelected property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isPidSelected() {
+            return pidSelected;
+        }
+
+        /**
+         * Sets the value of the pidSelected property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setPidSelected(Boolean value) {
+            this.pidSelected = value;
+        }
+
+        /**
+         * Gets the value of the bgColour property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getBgColour() {
+            return bgColour;
+        }
+
+        /**
+         * Sets the value of the bgColour property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setBgColour(String value) {
+            this.bgColour = value;
+        }
+
+        /**
+         * Gets the value of the consThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getConsThreshold() {
+            return consThreshold;
+        }
+
+        /**
+         * Sets the value of the consThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setConsThreshold(Integer value) {
+            this.consThreshold = value;
+        }
+
+        /**
+         * Gets the value of the pidThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getPidThreshold() {
+            return pidThreshold;
+        }
+
+        /**
+         * Sets the value of the pidThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setPidThreshold(Integer value) {
+            this.pidThreshold = value;
+        }
+
+        /**
+         * Gets the value of the title property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getTitle() {
+            return title;
+        }
+
+        /**
+         * Sets the value of the title property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setTitle(String value) {
+            this.title = value;
+        }
+
+        /**
+         * Gets the value of the showFullId property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowFullId() {
+            return showFullId;
+        }
+
+        /**
+         * Sets the value of the showFullId property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowFullId(Boolean value) {
+            this.showFullId = value;
+        }
+
+        /**
+         * Gets the value of the rightAlignIds property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isRightAlignIds() {
+            return rightAlignIds;
+        }
+
+        /**
+         * Sets the value of the rightAlignIds property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setRightAlignIds(Boolean value) {
+            this.rightAlignIds = value;
+        }
+
+        /**
+         * Gets the value of the showText property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowText() {
+            return showText;
+        }
+
+        /**
+         * Sets the value of the showText property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowText(Boolean value) {
+            this.showText = value;
+        }
+
+        /**
+         * Gets the value of the showColourText property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowColourText() {
+            return showColourText;
+        }
+
+        /**
+         * Sets the value of the showColourText property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowColourText(Boolean value) {
+            this.showColourText = value;
+        }
+
+        /**
+         * Gets the value of the showUnconserved property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowUnconserved() {
+            if (showUnconserved == null) {
+                return false;
+            } else {
+                return showUnconserved;
+            }
+        }
+
+        /**
+         * Sets the value of the showUnconserved property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowUnconserved(Boolean value) {
+            this.showUnconserved = value;
+        }
+
+        /**
+         * Gets the value of the showBoxes property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowBoxes() {
+            return showBoxes;
+        }
+
+        /**
+         * Sets the value of the showBoxes property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowBoxes(Boolean value) {
+            this.showBoxes = value;
+        }
+
+        /**
+         * Gets the value of the wrapAlignment property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isWrapAlignment() {
+            return wrapAlignment;
+        }
+
+        /**
+         * Sets the value of the wrapAlignment property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setWrapAlignment(Boolean value) {
+            this.wrapAlignment = value;
+        }
+
+        /**
+         * Gets the value of the renderGaps property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isRenderGaps() {
+            return renderGaps;
+        }
+
+        /**
+         * Sets the value of the renderGaps property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setRenderGaps(Boolean value) {
+            this.renderGaps = value;
+        }
+
+        /**
+         * Gets the value of the showSequenceFeatures property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowSequenceFeatures() {
+            return showSequenceFeatures;
+        }
+
+        /**
+         * Sets the value of the showSequenceFeatures property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowSequenceFeatures(Boolean value) {
+            this.showSequenceFeatures = value;
+        }
+
+        /**
+         * Gets the value of the showNPfeatureTooltip property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowNPfeatureTooltip() {
+            return showNPfeatureTooltip;
+        }
+
+        /**
+         * Sets the value of the showNPfeatureTooltip property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowNPfeatureTooltip(Boolean value) {
+            this.showNPfeatureTooltip = value;
+        }
+
+        /**
+         * Gets the value of the showDbRefTooltip property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowDbRefTooltip() {
+            return showDbRefTooltip;
+        }
+
+        /**
+         * Sets the value of the showDbRefTooltip property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowDbRefTooltip(Boolean value) {
+            this.showDbRefTooltip = value;
+        }
+
+        /**
+         * Gets the value of the followHighlight property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isFollowHighlight() {
+            if (followHighlight == null) {
+                return true;
+            } else {
+                return followHighlight;
+            }
+        }
+
+        /**
+         * Sets the value of the followHighlight property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setFollowHighlight(Boolean value) {
+            this.followHighlight = value;
+        }
+
+        /**
+         * Gets the value of the followSelection property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isFollowSelection() {
+            if (followSelection == null) {
+                return true;
+            } else {
+                return followSelection;
+            }
+        }
+
+        /**
+         * Sets the value of the followSelection property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setFollowSelection(Boolean value) {
+            this.followSelection = value;
+        }
+
+        /**
+         * Gets the value of the showAnnotation property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isShowAnnotation() {
+            return showAnnotation;
+        }
+
+        /**
+         * Sets the value of the showAnnotation property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowAnnotation(Boolean value) {
+            this.showAnnotation = value;
+        }
+
+        /**
+         * Gets the value of the centreColumnLabels property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isCentreColumnLabels() {
+            if (centreColumnLabels == null) {
+                return false;
+            } else {
+                return centreColumnLabels;
+            }
+        }
+
+        /**
+         * Sets the value of the centreColumnLabels property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setCentreColumnLabels(Boolean value) {
+            this.centreColumnLabels = value;
+        }
+
+        /**
+         * Gets the value of the showGroupConservation property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowGroupConservation() {
+            if (showGroupConservation == null) {
+                return false;
+            } else {
+                return showGroupConservation;
+            }
+        }
+
+        /**
+         * Sets the value of the showGroupConservation property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowGroupConservation(Boolean value) {
+            this.showGroupConservation = value;
+        }
+
+        /**
+         * Gets the value of the showGroupConsensus property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowGroupConsensus() {
+            if (showGroupConsensus == null) {
+                return false;
+            } else {
+                return showGroupConsensus;
+            }
+        }
+
+        /**
+         * Sets the value of the showGroupConsensus property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowGroupConsensus(Boolean value) {
+            this.showGroupConsensus = value;
+        }
+
+        /**
+         * Gets the value of the showConsensusHistogram property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowConsensusHistogram() {
+            if (showConsensusHistogram == null) {
+                return true;
+            } else {
+                return showConsensusHistogram;
+            }
+        }
+
+        /**
+         * Sets the value of the showConsensusHistogram property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowConsensusHistogram(Boolean value) {
+            this.showConsensusHistogram = value;
+        }
+
+        /**
+         * Gets the value of the showSequenceLogo property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isShowSequenceLogo() {
+            if (showSequenceLogo == null) {
+                return false;
+            } else {
+                return showSequenceLogo;
+            }
+        }
+
+        /**
+         * Sets the value of the showSequenceLogo property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setShowSequenceLogo(Boolean value) {
+            this.showSequenceLogo = value;
+        }
+
+        /**
+         * Gets the value of the normaliseSequenceLogo property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isNormaliseSequenceLogo() {
+            if (normaliseSequenceLogo == null) {
+                return false;
+            } else {
+                return normaliseSequenceLogo;
+            }
+        }
+
+        /**
+         * Sets the value of the normaliseSequenceLogo property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setNormaliseSequenceLogo(Boolean value) {
+            this.normaliseSequenceLogo = value;
+        }
+
+        /**
+         * Gets the value of the ignoreGapsinConsensus property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isIgnoreGapsinConsensus() {
+            if (ignoreGapsinConsensus == null) {
+                return true;
+            } else {
+                return ignoreGapsinConsensus;
+            }
+        }
+
+        /**
+         * Sets the value of the ignoreGapsinConsensus property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setIgnoreGapsinConsensus(Boolean value) {
+            this.ignoreGapsinConsensus = value;
+        }
+
+        /**
+         * Gets the value of the startRes property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getStartRes() {
+            return startRes;
+        }
+
+        /**
+         * Sets the value of the startRes property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setStartRes(Integer value) {
+            this.startRes = value;
+        }
+
+        /**
+         * Gets the value of the startSeq property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getStartSeq() {
+            return startSeq;
+        }
+
+        /**
+         * Sets the value of the startSeq property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setStartSeq(Integer value) {
+            this.startSeq = value;
+        }
+
+        /**
+         * Gets the value of the fontName property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getFontName() {
+            return fontName;
+        }
+
+        /**
+         * Sets the value of the fontName property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setFontName(String value) {
+            this.fontName = value;
+        }
+
+        /**
+         * Gets the value of the fontSize property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getFontSize() {
+            return fontSize;
+        }
+
+        /**
+         * Sets the value of the fontSize property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setFontSize(Integer value) {
+            this.fontSize = value;
+        }
+
+        /**
+         * Gets the value of the fontStyle property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getFontStyle() {
+            return fontStyle;
+        }
+
+        /**
+         * Sets the value of the fontStyle property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setFontStyle(Integer value) {
+            this.fontStyle = value;
+        }
+
+        /**
+         * Gets the value of the scaleProteinAsCdna property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public boolean isScaleProteinAsCdna() {
+            if (scaleProteinAsCdna == null) {
+                return true;
+            } else {
+                return scaleProteinAsCdna;
+            }
+        }
+
+        /**
+         * Sets the value of the scaleProteinAsCdna property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setScaleProteinAsCdna(Boolean value) {
+            this.scaleProteinAsCdna = value;
+        }
+
+        /**
+         * Gets the value of the viewName property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getViewName() {
+            return viewName;
+        }
+
+        /**
+         * Sets the value of the viewName property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setViewName(String value) {
+            this.viewName = value;
+        }
+
+        /**
+         * Gets the value of the sequenceSetId property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getSequenceSetId() {
+            return sequenceSetId;
+        }
+
+        /**
+         * Sets the value of the sequenceSetId property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setSequenceSetId(String value) {
+            this.sequenceSetId = value;
+        }
+
+        /**
+         * Gets the value of the gatheredViews property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Boolean }
+         *     
+         */
+        public Boolean isGatheredViews() {
+            return gatheredViews;
+        }
+
+        /**
+         * Sets the value of the gatheredViews property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Boolean }
+         *     
+         */
+        public void setGatheredViews(Boolean value) {
+            this.gatheredViews = value;
+        }
+
+        /**
+         * Gets the value of the textCol1 property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextCol1() {
+            return textCol1;
+        }
+
+        /**
+         * Sets the value of the textCol1 property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextCol1(Integer value) {
+            this.textCol1 = value;
+        }
+
+        /**
+         * Gets the value of the textCol2 property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextCol2() {
+            return textCol2;
+        }
+
+        /**
+         * Sets the value of the textCol2 property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextCol2(Integer value) {
+            this.textCol2 = value;
+        }
+
+        /**
+         * Gets the value of the textColThreshold property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getTextColThreshold() {
+            return textColThreshold;
+        }
+
+        /**
+         * Sets the value of the textColThreshold property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setTextColThreshold(Integer value) {
+            this.textColThreshold = value;
+        }
+
+        /**
+         * Gets the value of the id property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getId() {
+            return id;
+        }
+
+        /**
+         * Sets the value of the id property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setId(String value) {
+            this.id = value;
+        }
+
+        /**
+         * Gets the value of the complementId property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link String }
+         *     
+         */
+        public String getComplementId() {
+            return complementId;
+        }
+
+        /**
+         * Sets the value of the complementId property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link String }
+         *     
+         */
+        public void setComplementId(String value) {
+            this.complementId = value;
+        }
+
+        /**
+         * Gets the value of the width property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getWidth() {
+            return width;
+        }
+
+        /**
+         * Sets the value of the width property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setWidth(Integer value) {
+            this.width = value;
+        }
+
+        /**
+         * Gets the value of the height property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getHeight() {
+            return height;
+        }
+
+        /**
+         * Sets the value of the height property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setHeight(Integer value) {
+            this.height = value;
+        }
+
+        /**
+         * Gets the value of the xpos property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getXpos() {
+            return xpos;
+        }
+
+        /**
+         * Sets the value of the xpos property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setXpos(Integer value) {
+            this.xpos = value;
+        }
+
+        /**
+         * Gets the value of the ypos property.
+         * 
+         * @return
+         *     possible object is
+         *     {@link Integer }
+         *     
+         */
+        public Integer getYpos() {
+            return ypos;
+        }
+
+        /**
+         * Sets the value of the ypos property.
+         * 
+         * @param value
+         *     allowed object is
+         *     {@link Integer }
+         *     
+         */
+        public void setYpos(Integer value) {
+            this.ypos = value;
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
+         *       &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
+         *       &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
+         *     &lt;/extension>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "")
+        public static class CalcIdParam
+            extends WebServiceParameterSet
+        {
+
+            @XmlAttribute(name = "calcId", required = true)
+            protected String calcId;
+            @XmlAttribute(name = "needsUpdate")
+            protected Boolean needsUpdate;
+            @XmlAttribute(name = "autoUpdate", required = true)
+            protected boolean autoUpdate;
+
+            /**
+             * Gets the value of the calcId property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link String }
+             *     
+             */
+            public String getCalcId() {
+                return calcId;
+            }
+
+            /**
+             * Sets the value of the calcId property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link String }
+             *     
+             */
+            public void setCalcId(String value) {
+                this.calcId = value;
+            }
+
+            /**
+             * Gets the value of the needsUpdate property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Boolean }
+             *     
+             */
+            public boolean isNeedsUpdate() {
+                if (needsUpdate == null) {
+                    return false;
+                } else {
+                    return needsUpdate;
+                }
+            }
+
+            /**
+             * Sets the value of the needsUpdate property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Boolean }
+             *     
+             */
+            public void setNeedsUpdate(Boolean value) {
+                this.needsUpdate = value;
+            }
+
+            /**
+             * Gets the value of the autoUpdate property.
+             * 
+             */
+            public boolean isAutoUpdate() {
+                return autoUpdate;
+            }
+
+            /**
+             * Sets the value of the autoUpdate property.
+             * 
+             */
+            public void setAutoUpdate(boolean value) {
+                this.autoUpdate = value;
+            }
+
+        }
+
+
+        /**
+         * <p>Java class for anonymous complex type.
+         * 
+         * <p>The following schema fragment specifies the expected content contained within this class.
+         * 
+         * <pre>
+         * &lt;complexType>
+         *   &lt;complexContent>
+         *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *       &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *     &lt;/restriction>
+         *   &lt;/complexContent>
+         * &lt;/complexType>
+         * </pre>
+         * 
+         * 
+         */
+        @XmlAccessorType(XmlAccessType.FIELD)
+        @XmlType(name = "")
+        public static class HiddenColumns {
+
+            @XmlAttribute(name = "start")
+            protected Integer start;
+            @XmlAttribute(name = "end")
+            protected Integer end;
+
+            /**
+             * Gets the value of the start property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getStart() {
+                return start;
+            }
+
+            /**
+             * Sets the value of the start property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setStart(Integer value) {
+                this.start = value;
+            }
+
+            /**
+             * Gets the value of the end property.
+             * 
+             * @return
+             *     possible object is
+             *     {@link Integer }
+             *     
+             */
+            public Integer getEnd() {
+                return end;
+            }
+
+            /**
+             * Sets the value of the end property.
+             * 
+             * @param value
+             *     allowed object is
+             *     {@link Integer }
+             *     
+             */
+            public void setEnd(Integer value) {
+                this.end = value;
+            }
+
+        }
+
+    }
+
+}
index 313ec1c..fe6d962 100755 (executable)
@@ -119,6 +119,7 @@ import java.util.Map;
  * 
  */
 public class EpsGraphics2D extends java.awt.Graphics2D
+        implements AutoCloseable
 {
 
   public static final String VERSION = "0.8.8";
@@ -250,6 +251,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * OutputStream is automatically flushed before being closed. If you forget to
    * do this, the file may be incomplete.
    */
+  @Override
   public void close() throws IOException
   {
     flush();
@@ -402,6 +404,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Draws a 3D rectangle outline. If it is raised, light appears to come from
    * the top left.
    */
+  @Override
   public void draw3DRect(int x, int y, int width, int height, boolean raised)
   {
     Color originalColor = getColor();
@@ -441,6 +444,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Fills a 3D rectangle. If raised, it has bright fill and light appears to
    * come from the top left.
    */
+  @Override
   public void fill3DRect(int x, int y, int width, int height, boolean raised)
   {
     Color originalColor = getColor();
@@ -461,6 +465,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a Shape on the EPS document.
    */
+  @Override
   public void draw(Shape s)
   {
     draw(s, "stroke");
@@ -469,6 +474,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an Image on the EPS document.
    */
+  @Override
   public boolean drawImage(Image img, AffineTransform xform,
           ImageObserver obs)
   {
@@ -482,6 +488,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a BufferedImage on the EPS document.
    */
+  @Override
   public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
   {
     BufferedImage img1 = op.filter(img, null);
@@ -491,6 +498,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a RenderedImage on the EPS document.
    */
+  @Override
   public void drawRenderedImage(RenderedImage img, AffineTransform xform)
   {
     Hashtable properties = new Hashtable();
@@ -513,6 +521,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a RenderableImage by invoking its createDefaultRendering method.
    */
+  @Override
   public void drawRenderableImage(RenderableImage img, AffineTransform xform)
   {
     drawRenderedImage(img.createDefaultRendering(), xform);
@@ -521,6 +530,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a string at (x,y)
    */
+  @Override
   public void drawString(String str, int x, int y)
   {
     drawString(str, (float) x, (float) y);
@@ -529,6 +539,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a string at (x,y)
    */
+  @Override
   public void drawString(String s, float x, float y)
   {
     if (s != null && s.length() > 0)
@@ -543,6 +554,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Draws the characters of an AttributedCharacterIterator, starting from
    * (x,y).
    */
+  @Override
   public void drawString(AttributedCharacterIterator iterator, int x, int y)
   {
     drawString(iterator, (float) x, (float) y);
@@ -552,6 +564,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Draws the characters of an AttributedCharacterIterator, starting from
    * (x,y).
    */
+  @Override
   public void drawString(AttributedCharacterIterator iterator, float x,
           float y)
   {
@@ -584,6 +597,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a GlyphVector at (x,y)
    */
+  @Override
   public void drawGlyphVector(GlyphVector g, float x, float y)
   {
     Shape shape = g.getOutline(x, y);
@@ -593,6 +607,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills a Shape on the EPS document.
    */
+  @Override
   public void fill(Shape s)
   {
     draw(s, "fill");
@@ -602,6 +617,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Checks whether or not the specified Shape intersects the specified
    * Rectangle, which is in device space.
    */
+  @Override
   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
   {
     return s.intersects(rect);
@@ -610,6 +626,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the device configuration associated with this EpsGraphics2D object.
    */
+  @Override
   public GraphicsConfiguration getDeviceConfiguration()
   {
     GraphicsConfiguration gc = null;
@@ -632,6 +649,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Sets the Composite to be used by this EpsGraphics2D. EpsGraphics2D does not
    * make use of these.
    */
+  @Override
   public void setComposite(Composite comp)
   {
     _composite = comp;
@@ -641,6 +659,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Sets the Paint attribute for the EpsGraphics2D object. Only Paint objects
    * of type Color are respected by EpsGraphics2D.
    */
+  @Override
   public void setPaint(Paint paint)
   {
     _paint = paint;
@@ -654,6 +673,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Sets the stroke. Only accepts BasicStroke objects (or subclasses of
    * BasicStroke).
    */
+  @Override
   public void setStroke(Stroke s)
   {
     if (s instanceof BasicStroke)
@@ -688,6 +708,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets a rendering hint. These are not used by EpsGraphics2D.
    */
+  @Override
   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
   {
     // Do nothing.
@@ -697,6 +718,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Returns the value of a single preference for the rendering algorithms.
    * Rendering hints are not used by EpsGraphics2D.
    */
+  @Override
   public Object getRenderingHint(RenderingHints.Key hintKey)
   {
     return null;
@@ -705,6 +727,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the rendering hints. These are ignored by EpsGraphics2D.
    */
+  @Override
   public void setRenderingHints(Map hints)
   {
     // Do nothing.
@@ -713,6 +736,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Adds rendering hints. These are ignored by EpsGraphics2D.
    */
+  @Override
   public void addRenderingHints(Map hints)
   {
     // Do nothing.
@@ -721,6 +745,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the preferences for the rendering algorithms.
    */
+  @Override
   public RenderingHints getRenderingHints()
   {
     return new RenderingHints(null);
@@ -730,6 +755,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Translates the origin of the EpsGraphics2D context to the point (x,y) in
    * the current coordinate system.
    */
+  @Override
   public void translate(int x, int y)
   {
     translate((double) x, (double) y);
@@ -739,6 +765,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Concatenates the current EpsGraphics2D Transformation with a translation
    * transform.
    */
+  @Override
   public void translate(double tx, double ty)
   {
     transform(AffineTransform.getTranslateInstance(tx, ty));
@@ -747,6 +774,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Concatenates the current EpsGraphics2D Transform with a rotation transform.
    */
+  @Override
   public void rotate(double theta)
   {
     rotate(theta, 0, 0);
@@ -756,6 +784,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Concatenates the current EpsGraphics2D Transform with a translated rotation
    * transform.
    */
+  @Override
   public void rotate(double theta, double x, double y)
   {
     transform(AffineTransform.getRotateInstance(theta, x, y));
@@ -765,6 +794,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Concatenates the current EpsGraphics2D Transform with a scaling
    * transformation.
    */
+  @Override
   public void scale(double sx, double sy)
   {
     transform(AffineTransform.getScaleInstance(sx, sy));
@@ -773,6 +803,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Concatenates the current EpsGraphics2D Transform with a shearing transform.
    */
+  @Override
   public void shear(double shx, double shy)
   {
     transform(AffineTransform.getShearInstance(shx, shy));
@@ -782,6 +813,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Composes an AffineTransform object with the Transform in this EpsGraphics2D
    * according to the rule last-specified-first-applied.
    */
+  @Override
   public void transform(AffineTransform Tx)
   {
     _transform.concatenate(Tx);
@@ -791,6 +823,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the AffineTransform to be used by this EpsGraphics2D.
    */
+  @Override
   public void setTransform(AffineTransform Tx)
   {
     if (Tx == null)
@@ -809,6 +842,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Gets the AffineTransform used by this EpsGraphics2D.
    */
+  @Override
   public AffineTransform getTransform()
   {
     return new AffineTransform(_transform);
@@ -817,6 +851,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the current Paint of the EpsGraphics2D object.
    */
+  @Override
   public Paint getPaint()
   {
     return _paint;
@@ -825,6 +860,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * returns the current Composite of the EpsGraphics2D object.
    */
+  @Override
   public Composite getComposite()
   {
     return _composite;
@@ -833,6 +869,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the background color to be used by the clearRect method.
    */
+  @Override
   public void setBackground(Color color)
   {
     if (color == null)
@@ -845,6 +882,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Gets the background color that is used by the clearRect method.
    */
+  @Override
   public Color getBackground()
   {
     return _backgroundColor;
@@ -854,6 +892,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Returns the Stroke currently used. Guaranteed to be an instance of
    * BasicStroke.
    */
+  @Override
   public Stroke getStroke()
   {
     return _stroke;
@@ -863,6 +902,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Intersects the current clip with the interior of the specified Shape and
    * sets the clip to the resulting intersection.
    */
+  @Override
   public void clip(Shape s)
   {
     if (_clip == null)
@@ -880,6 +920,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the FontRenderContext.
    */
+  @Override
   public FontRenderContext getFontRenderContext()
   {
     return _fontRenderContext;
@@ -890,6 +931,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns a new Graphics object that is identical to this EpsGraphics2D.
    */
+  @Override
   public Graphics create()
   {
     return new EpsGraphics2D(this);
@@ -899,6 +941,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Returns an EpsGraphics2D object based on this Graphics object, but with a
    * new translation and clip area.
    */
+  @Override
   public Graphics create(int x, int y, int width, int height)
   {
     Graphics g = create();
@@ -911,6 +954,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Returns the current Color. This will be a default value (black) until it is
    * changed using the setColor method.
    */
+  @Override
   public Color getColor()
   {
     return _color;
@@ -919,6 +963,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the Color to be used when drawing all future shapes, text, etc.
    */
+  @Override
   public void setColor(Color c)
   {
     if (c == null)
@@ -934,6 +979,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Sets the paint mode of this EpsGraphics2D object to overwrite the
    * destination EpsDocument with the current color.
    */
+  @Override
   public void setPaintMode()
   {
     // Do nothing - paint mode is the only method supported anyway.
@@ -943,6 +989,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * <b><i><font color="red">Not implemented</font></i></b> - performs no
    * action.
    */
+  @Override
   public void setXORMode(Color c1)
   {
     methodNotSupported();
@@ -951,6 +998,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the Font currently being used.
    */
+  @Override
   public Font getFont()
   {
     return _font;
@@ -959,6 +1007,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the Font to be used in future text.
    */
+  @Override
   public void setFont(Font font)
   {
     if (font == null)
@@ -966,13 +1015,14 @@ public class EpsGraphics2D extends java.awt.Graphics2D
       font = Font.decode(null);
     }
     _font = font;
-    append("/" + _font.getPSName() + " findfont " + ((int) _font.getSize())
+    append("/" + _font.getPSName() + " findfont " + (_font.getSize())
             + " scalefont setfont");
   }
 
   /**
    * Gets the font metrics of the current font.
    */
+  @Override
   public FontMetrics getFontMetrics()
   {
     return getFontMetrics(getFont());
@@ -981,6 +1031,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Gets the font metrics for the specified font.
    */
+  @Override
   public FontMetrics getFontMetrics(Font f)
   {
     BufferedImage image = new BufferedImage(1, 1,
@@ -992,6 +1043,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the bounding rectangle of the current clipping area.
    */
+  @Override
   public Rectangle getClipBounds()
   {
     if (_clip == null)
@@ -1005,6 +1057,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Intersects the current clip with the specified rectangle.
    */
+  @Override
   public void clipRect(int x, int y, int width, int height)
   {
     clip(new Rectangle(x, y, width, height));
@@ -1013,6 +1066,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the current clip to the rectangle specified by the given coordinates.
    */
+  @Override
   public void setClip(int x, int y, int width, int height)
   {
     setClip(new Rectangle(x, y, width, height));
@@ -1021,6 +1075,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Gets the current clipping area.
    */
+  @Override
   public Shape getClip()
   {
     if (_clip == null)
@@ -1046,6 +1101,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Sets the current clipping area to an arbitrary clip shape.
    */
+  @Override
   public void setClip(Shape clip)
   {
     if (clip != null)
@@ -1079,6 +1135,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * <b><i><font color="red">Not implemented</font></i></b> - performs no
    * action.
    */
+  @Override
   public void copyArea(int x, int y, int width, int height, int dx, int dy)
   {
     methodNotSupported();
@@ -1087,6 +1144,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a straight line from (x1,y1) to (x2,y2).
    */
+  @Override
   public void drawLine(int x1, int y1, int x2, int y2)
   {
     Shape shape = new Line2D.Float(x1, y1, x2, y2);
@@ -1096,6 +1154,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills a rectangle with top-left corner placed at (x,y).
    */
+  @Override
   public void fillRect(int x, int y, int width, int height)
   {
     Shape shape = new Rectangle(x, y, width, height);
@@ -1105,6 +1164,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a rectangle with top-left corner placed at (x,y).
    */
+  @Override
   public void drawRect(int x, int y, int width, int height)
   {
     Shape shape = new Rectangle(x, y, width, height);
@@ -1115,6 +1175,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Clears a rectangle with top-left corner placed at (x,y) using the current
    * background color.
    */
+  @Override
   public void clearRect(int x, int y, int width, int height)
   {
     Color originalColor = getColor();
@@ -1129,6 +1190,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a rounded rectangle.
    */
+  @Override
   public void drawRoundRect(int x, int y, int width, int height,
           int arcWidth, int arcHeight)
   {
@@ -1140,6 +1202,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills a rounded rectangle.
    */
+  @Override
   public void fillRoundRect(int x, int y, int width, int height,
           int arcWidth, int arcHeight)
   {
@@ -1151,6 +1214,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an oval.
    */
+  @Override
   public void drawOval(int x, int y, int width, int height)
   {
     Shape shape = new Ellipse2D.Float(x, y, width, height);
@@ -1160,6 +1224,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills an oval.
    */
+  @Override
   public void fillOval(int x, int y, int width, int height)
   {
     Shape shape = new Ellipse2D.Float(x, y, width, height);
@@ -1169,6 +1234,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an arc.
    */
+  @Override
   public void drawArc(int x, int y, int width, int height, int startAngle,
           int arcAngle)
   {
@@ -1180,6 +1246,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills an arc.
    */
+  @Override
   public void fillArc(int x, int y, int width, int height, int startAngle,
           int arcAngle)
   {
@@ -1191,6 +1258,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a polyline.
    */
+  @Override
   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
   {
     if (nPoints > 0)
@@ -1208,6 +1276,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a polygon made with the specified points.
    */
+  @Override
   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
   {
     Shape shape = new Polygon(xPoints, yPoints, nPoints);
@@ -1217,6 +1286,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws a polygon.
    */
+  @Override
   public void drawPolygon(Polygon p)
   {
     draw(p);
@@ -1225,6 +1295,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills a polygon made with the specified points.
    */
+  @Override
   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
   {
     Shape shape = new Polygon(xPoints, yPoints, nPoints);
@@ -1234,6 +1305,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Fills a polygon.
    */
+  @Override
   public void fillPolygon(Polygon p)
   {
     draw(p, "fill");
@@ -1242,6 +1314,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws the specified characters, starting from (x,y)
    */
+  @Override
   public void drawChars(char[] data, int offset, int length, int x, int y)
   {
     String string = new String(data, offset, length);
@@ -1251,6 +1324,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws the specified bytes, starting from (x,y)
    */
+  @Override
   public void drawBytes(byte[] data, int offset, int length, int x, int y)
   {
     String string = new String(data, offset, length);
@@ -1260,6 +1334,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
   {
     return drawImage(img, x, y, Color.white, observer);
@@ -1268,6 +1343,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int x, int y, int width, int height,
           ImageObserver observer)
   {
@@ -1277,6 +1353,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int x, int y, Color bgcolor,
           ImageObserver observer)
   {
@@ -1288,6 +1365,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int x, int y, int width, int height,
           Color bgcolor, ImageObserver observer)
   {
@@ -1298,6 +1376,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
           int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
   {
@@ -1308,6 +1387,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Draws an image.
    */
+  @Override
   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
           int sx1, int sy1, int sx2, int sy2, Color bgcolor,
           ImageObserver observer)
@@ -1403,24 +1483,29 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * only remaining EpsGraphics2D instance pointing at a EpsDocument object,
    * then the EpsDocument object shall become eligible for garbage collection.
    */
+  @Override
   public void dispose()
   {
     _document = null;
   }
 
+  /* bsoares 2019-03-20
+   * finalize is now deprecated. Implementing AutoCloseable instead
   /**
    * Finalizes the object.
-   */
+  @Override
   public void finalize()
   {
     super.finalize();
   }
+   */
 
   /**
    * Returns the entire contents of the EPS document, complete with headers and
    * bounding box. The returned String is suitable for being written directly to
    * disk as an EPS file.
    */
+  @Override
   public String toString()
   {
     StringWriter writer = new StringWriter();
@@ -1440,6 +1525,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
    * Returns true if the specified rectangular area might intersect the current
    * clipping area.
    */
+  @Override
   public boolean hitClip(int x, int y, int width, int height)
   {
     if (_clip == null)
@@ -1453,6 +1539,7 @@ public class EpsGraphics2D extends java.awt.Graphics2D
   /**
    * Returns the bounding rectangle of the current clipping area.
    */
+  @Override
   public Rectangle getClipBounds(Rectangle r)
   {
     if (_clip == null)
index fe1c70c..ba8f8b0 100755 (executable)
@@ -296,8 +296,8 @@ public class InputParams implements java.io.Serializable
     {
       _hashCode += getMatrix().hashCode();
     }
-    _hashCode += new Float(getExp()).hashCode();
-    _hashCode += new Boolean(isEchofilter()).hashCode();
+    _hashCode += Float.valueOf(getExp()).hashCode();
+    _hashCode += Boolean.valueOf(isEchofilter()).hashCode();
     if (getFilter() != null)
     {
       _hashCode += getFilter().hashCode();
@@ -325,7 +325,7 @@ public class InputParams implements java.io.Serializable
       _hashCode += getOutformat().hashCode();
     }
     _hashCode += getTopcombon();
-    _hashCode += new Boolean(isAsync()).hashCode();
+    _hashCode += Boolean.valueOf(isAsync()).hashCode();
     if (getEmail() != null)
     {
       _hashCode += getEmail().hashCode();
index 5391221..f06a80a 100644 (file)
@@ -285,7 +285,7 @@ public class AccessionMapperBindingStub extends org.apache.axis.client.Stub
     setAttachments(_call);
     java.lang.Object _resp = _call.invoke(new java.lang.Object[] {
         sequence, searchDatabases, taxonId,
-        new java.lang.Boolean(onlyActive) });
+        java.lang.Boolean.valueOf(onlyActive) });
 
     if (_resp instanceof java.rmi.RemoteException)
     {
@@ -332,7 +332,7 @@ public class AccessionMapperBindingStub extends org.apache.axis.client.Stub
     setAttachments(_call);
     java.lang.Object _resp = _call.invoke(new java.lang.Object[] {
         accession, ac_version, searchDatabases, taxonId,
-        new java.lang.Boolean(onlyActive) });
+        java.lang.Boolean.valueOf(onlyActive) });
 
     if (_resp instanceof java.rmi.RemoteException)
     {
index 0424acc..9d8a993 100644 (file)
  */
 package jalview.bin;
 
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
 import jalview.gui.JvOptionPane;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 
 import org.testng.Assert;
@@ -36,6 +41,7 @@ import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ModuleRef;
 import io.github.classgraph.ScanResult;
 
 public class CommandLineOperations
@@ -48,7 +54,8 @@ public class CommandLineOperations
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  private static final int TEST_TIMEOUT = 9000; // Note longer timeout needed on
+  private static final int TEST_TIMEOUT = 9000; // Note longer timeout needed
+                                                // on
                                                 // full test run than on
                                                 // individual tests
 
@@ -120,6 +127,10 @@ public class CommandLineOperations
 
   private static String classpath = null;
 
+  private static String modules = null;
+
+  private static String java_exe = null;
+
   public synchronized static String getClassPath()
   {
     if (scanner == null)
@@ -127,6 +138,14 @@ public class CommandLineOperations
       scanner = new ClassGraph();
       ScanResult scan = scanner.scan();
       classpath = scan.getClasspath();
+      modules = "";
+      for (ModuleRef mr : scan.getModules())
+      {
+        modules.concat(mr.getName());
+      }
+      java_exe = System.getProperty("java.home") + File.separator + "bin"
+              + File.separator + "java";
+
     }
     while (classpath == null)
     {
@@ -140,21 +159,25 @@ public class CommandLineOperations
     }
     return classpath;
   }
-  private Worker jalviewDesktopRunner(boolean withAwt, String cmd,
+
+  private Worker getJalviewDesktopRunner(boolean withAwt, String cmd,
           int timeout)
   {
     // Note: JAL-3065 - don't include quotes for lib/* because the arguments are
     // not expanded by the shell
     String classpath = getClassPath();
-    String _cmd = "java "
+    String _cmd = java_exe + " "
             + (withAwt ? "-Djava.awt.headless=true" : "")
-            + " -classpath " + classpath + " jalview.bin.Jalview ";
+            + " -classpath " + classpath
+            + (modules.length() > 2 ? "--add-modules=\"" + modules + "\""
+                    : "")
+            + " jalview.bin.Jalview ";
     Process ls2_proc = null;
     Worker worker = null;
     try
     {
       ls2_proc = Runtime.getRuntime().exec(_cmd + cmd);
-    } catch (IOException e1)
+    } catch (Throwable e1)
     {
       e1.printStackTrace();
     }
@@ -171,7 +194,7 @@ public class CommandLineOperations
         worker.join(timeout);
       } catch (InterruptedException e)
       {
-        // e.printStackTrace();
+        System.err.println("Thread interrupted");
       }
       worker.setOutputReader(outputReader);
       worker.setErrorReader(errorReader);
@@ -179,6 +202,20 @@ public class CommandLineOperations
     return worker;
   }
 
+  @Test(groups = { "Functional" })
+  public void reportCurrentWorkingDirectory()
+  {
+    try
+    {
+      Path currentRelativePath = Paths.get("");
+      String s = currentRelativePath.toAbsolutePath().toString();
+      System.out.println("Test CWD is " + s);
+    } catch (Exception q)
+    {
+      q.printStackTrace();
+    }
+  }
+
   @BeforeTest(alwaysRun = true)
   public void initialize()
   {
@@ -189,23 +226,27 @@ public class CommandLineOperations
   public void setUpForHeadlessCommandLineInputOperations()
           throws IOException
   {
-    String cmds = "nodisplay -open examples/uniref50.fa -sortbytree -props FILE -colour zappo "
-            + "-jabaws http://www.compbio.dundee.ac.uk/jabaws -nosortbytree -dasserver nickname=www.test.com "
+    String cmds = "nodisplay -open examples/uniref50.fa -sortbytree -props test/jalview/io/testProps.jvprops -colour zappo "
+            + "-jabaws http://www.compbio.dundee.ac.uk/jabaws -nosortbytree "
             + "-features examples/testdata/plantfdx.features -annotations examples/testdata/plantfdx.annotations -tree examples/testdata/uniref50_test_tree";
-    Worker worker = jalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
+    Worker worker = getJalviewDesktopRunner(true, cmds, SETUP_TIMEOUT);
     String ln = null;
     while ((ln = worker.getOutputReader().readLine()) != null)
     {
       System.out.println(ln);
       successfulCMDs.add(ln);
     }
+    while ((ln = worker.getErrorReader().readLine()) != null)
+    {
+      System.err.println(ln);
+    }
   }
 
   @BeforeTest(alwaysRun = true)
   public void setUpForCommandLineInputOperations() throws IOException
   {
     String cmds = "-open examples/uniref50.fa -noquestionnaire -nousagestats";
-    Worker worker = jalviewDesktopRunner(false, cmds, SETUP_TIMEOUT);
+    Worker worker = getJalviewDesktopRunner(false, cmds, SETUP_TIMEOUT);
     String ln = null;
     int count = 0;
     while ((ln = worker.getErrorReader().readLine()) != null)
@@ -225,7 +266,7 @@ public class CommandLineOperations
     }
   }
 
-  @Test(groups = { "Functional" }, dataProvider = "allInputOpearationsData")
+  @Test(groups = { "Functional" }, dataProvider = "allInputOperationsData")
   public void testAllInputOperations(String expectedString,
           String failureMsg)
   {
@@ -233,7 +274,8 @@ public class CommandLineOperations
   }
 
   @Test(
-    groups = { "Functional" },
+    groups =
+    { "Functional", "testben" },
     dataProvider = "headlessModeOutputOperationsData")
   public void testHeadlessModeOutputOperations(String harg, String type,
           String fileName, boolean withAWT, int expectedMinFileSize,
@@ -242,95 +284,110 @@ public class CommandLineOperations
     String cmd = harg + type + " " + fileName;
     // System.out.println(">>>>>>>>>>>>>>>> Command : " + cmd);
     File file = new File(fileName);
-    Worker worker = jalviewDesktopRunner(withAWT, cmd, timeout);
-
-    FileAssert.assertFile(file, "Didn't create an output" + type
-            + " file.[" + harg + "]");
-    FileAssert.assertMinLength(new File(fileName), expectedMinFileSize);
+    file.deleteOnExit();
+    Worker worker = getJalviewDesktopRunner(withAWT, cmd, timeout);
+    assertNotNull(worker, "worker is null");
+    String msg = "Didn't create an output" + type + " file.[" + harg + "]";
+    assertTrue(file.exists(), msg);
+    FileAssert.assertFile(file, msg);
+    FileAssert.assertMinLength(file, expectedMinFileSize);
     if (worker != null && worker.exit == null)
     {
       worker.interrupt();
       Thread.currentThread().interrupt();
       worker.process.destroy();
-      Assert.fail("Jalview did not exit after "
-              + type
+      Assert.fail("Jalview did not exit after " + type
               + " generation (try running test again to verify - timeout at "
-              + SETUP_TIMEOUT + "ms). ["
-              + harg + "]");
+              + timeout + "ms). [" + harg + "]");
     }
-    new File(fileName).delete();
+    file.delete();
   }
 
-  @DataProvider(name = "allInputOpearationsData")
+  @DataProvider(name = "allInputOperationsData")
   public Object[][] getHeadlessModeInputParams()
   {
     return new Object[][] {
         // headless mode input operations
         { "CMD [-color zappo] executed successfully!",
             "Failed command : -color zappo" },
-        { "CMD [-props FILE] executed successfully!",
+        { "CMD [-props test/jalview/io/testProps.jvprops] executed successfully!",
             "Failed command : -props File" },
         { "CMD [-sortbytree] executed successfully!",
             "Failed command : -sortbytree" },
-        {
-            "CMD [-jabaws http://www.compbio.dundee.ac.uk/jabaws] executed successfully!",
+        { "CMD [-jabaws http://www.compbio.dundee.ac.uk/jabaws] executed successfully!",
             "Failed command : -jabaws http://www.compbio.dundee.ac.uk/jabaws" },
         { "CMD [-open examples/uniref50.fa] executed successfully!",
             "Failed command : -open examples/uniref50.fa" },
         { "CMD [-nosortbytree] executed successfully!",
             "Failed command : -nosortbytree" },
-        {
-            "CMD [-features examples/testdata/plantfdx.features]  executed successfully!",
+        { "CMD [-features examples/testdata/plantfdx.features]  executed successfully!",
             "Failed command : -features examples/testdata/plantfdx.features" },
-        {
-            "CMD [-annotations examples/testdata/plantfdx.annotations] executed successfully!",
+        { "CMD [-annotations examples/testdata/plantfdx.annotations] executed successfully!",
             "Failed command : -annotations examples/testdata/plantfdx.annotations" },
-        {
-            "CMD [-tree examples/testdata/uniref50_test_tree] executed successfully!",
+        { "CMD [-tree examples/testdata/uniref50_test_tree] executed successfully!",
             "Failed command : -tree examples/testdata/uniref50_test_tree" },
         // non headless mode input operations
         { "CMD [-nousagestats] executed successfully!",
             "Failed command : -nousagestats" },
         { "CMD [-noquestionnaire] executed successfully!",
             "Failed command : -noquestionnaire" } };
-
   }
 
   @DataProvider(name = "headlessModeOutputOperationsData")
   public static Object[][] getHeadlessModeOutputParams()
   {
-    return new Object[][] {
-        { "nodisplay -open examples/uniref50.fa", " -eps",
-            "test_uniref50_out.eps", true, MINFILESIZE_BIG, TEST_TIMEOUT },
+    // JBPNote: I'm not clear why need to specify full path for output file
+    // when running tests on build server, but we will keep this patch for now
+    // since it works.
+    // https://issues.jalview.org/browse/JAL-1889?focusedCommentId=21609&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-21609
+    String workingDir = "test/jalview/bin/";
+    return new Object[][] { { "nodisplay -open examples/uniref50.fa",
+        " -eps", workingDir + "test_uniref50_out.eps", true,
+        MINFILESIZE_BIG, TEST_TIMEOUT },
         { "nodisplay -open examples/uniref50.fa", " -eps",
-            "test_uniref50_out.eps", false, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.eps", false,
+            MINFILESIZE_BIG, TEST_TIMEOUT },
         { "nogui -open examples/uniref50.fa", " -eps",
-            "test_uniref50_out.eps", true, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
         { "nogui -open examples/uniref50.fa", " -eps",
-            "test_uniref50_out.eps", false, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.eps", false,
+            MINFILESIZE_BIG, TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -eps",
-            "test_uniref50_out.eps", true, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.eps", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -svg",
-            "test_uniref50_out.svg", false, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.svg", false,
+            MINFILESIZE_BIG, TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -png",
-            "test_uniref50_out.png", true, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.png", true, MINFILESIZE_BIG,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -html",
-            "test_uniref50_out.html", true, MINFILESIZE_BIG, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.html", true,
+            MINFILESIZE_BIG, TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -fasta",
-            "test_uniref50_out.mfa", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.mfa", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -clustal",
-            "test_uniref50_out.aln", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -msf",
-            "test_uniref50_out.msf", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.msf", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -pileup",
-            "test_uniref50_out.aln", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.aln", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -pir",
-            "test_uniref50_out.pir", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.pir", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -pfam",
-            "test_uniref50_out.pfam", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.pfam", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -blc",
-            "test_uniref50_out.blc", true, MINFILESIZE_SMALL, TEST_TIMEOUT },
+            workingDir + "test_uniref50_out.blc", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT },
         { "headless -open examples/uniref50.fa", " -jalview",
-            "test_uniref50_out.jvp", true, MINFILESIZE_SMALL, TEST_TIMEOUT }, };
+            workingDir + "test_uniref50_out.jvp", true, MINFILESIZE_SMALL,
+            TEST_TIMEOUT }, };
   }
 }
index 2dda4d3..aefcbc0 100644 (file)
@@ -103,8 +103,8 @@ public class ColumnSelectionTest
     cs.removeElement(1);
     List<Integer> sel = cs.getSelected();
     assertEquals(2, sel.size());
-    assertEquals(new Integer(2), sel.get(0));
-    assertEquals(new Integer(5), sel.get(1));
+    assertEquals(Integer.valueOf(2), sel.get(0));
+    assertEquals(Integer.valueOf(5), sel.get(1));
 
     // removing an element in the list removes it
     cs.removeElement(2);
@@ -112,7 +112,7 @@ public class ColumnSelectionTest
     assertEquals(1, sel.size());
     sel = cs.getSelected();
     assertEquals(1, sel.size());
-    assertEquals(new Integer(5), sel.get(0));
+    assertEquals(Integer.valueOf(5), sel.get(0));
   }
 
   /**
index c955979..9111e5a 100644 (file)
@@ -48,7 +48,7 @@ public class SequenceFeatureTest
             12.5f, "group");
     sf1.setValue("STRAND", "+");
     sf1.setValue("Note", "Testing");
-    Integer count = new Integer(7);
+    Integer count = Integer.valueOf(7);
     sf1.setValue("Count", count);
 
     SequenceFeature sf2 = new SequenceFeature(sf1);
@@ -106,7 +106,7 @@ public class SequenceFeatureTest
     assertEquals("+", sf1.getValue("STRAND"));
     assertNull(sf1.getValue("strand")); // case-sensitive
     assertEquals(".", sf1.getValue("unknown", "."));
-    Integer i = new Integer(27);
+    Integer i = Integer.valueOf(27);
     assertSame(i, sf1.getValue("Unknown", i));
   }
 
index e685155..5128fe5 100644 (file)
@@ -94,7 +94,7 @@ public class ColourMenuHelperTest
        * check i18n for display name
        */
       String label = MessageManager.getStringOrReturn("label.colourScheme_",
-              name.toLowerCase().replace(" ", "_"));
+              name);
       assertEquals(item.getText(), label);
     }
 
@@ -164,7 +164,7 @@ public class ColourMenuHelperTest
        * check i18n for display name
        */
       String label = MessageManager.getStringOrReturn("label.colourScheme_",
-              name.toLowerCase().replace(" ", "_"));
+              name);
       assertEquals(item.getText(), label);
     }
   
index 6f60588..df30935 100644 (file)
@@ -489,115 +489,76 @@ public class PopupMenuTest
   }
 
   /**
-   * Test for adding feature links
+   * Test for adding sequence id, dbref and feature links
    */
   @Test(groups = { "Functional" })
-  public void testAddFeatureLinks()
+  public void testBuildLinkMenu()
   {
-    // sequences from the alignment
     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
-
-    // create list of links and list of DBRefs
-    List<String> links = new ArrayList<>();
-    List<DBRefEntry> refs = new ArrayList<>();
-
-    // links as might be added into Preferences | Connections dialog
-    links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
-            + SEQUENCE_ID + "$");
-    links.add("UNIPROT | http://www.uniprot.org/uniprot/$" + DB_ACCESSION
-            + "$");
-    links.add("INTERPRO | http://www.ebi.ac.uk/interpro/entry/$"
-            + DB_ACCESSION + "$");
-    // Gene3D entry tests for case (in)sensitivity
-    links.add("Gene3D | http://gene3d.biochem.ucl.ac.uk/Gene3D/search?sterm=$"
-            + DB_ACCESSION + "$&mode=protein");
-
-    // make seq0 dbrefs
-    refs.add(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR001041"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR006058"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR012675"));
-    
-    // make seq1 dbrefs
-    refs.add(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
-    refs.add(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
-
-    // add all the dbrefs to the sequences: Uniprot 1 each, Interpro all 3 to
-    // seq0, Gene3D to seq1
-    SequenceI seq = seqs.get(0);
-    seq.addDBRef(refs.get(0));
-
-    seq.addDBRef(refs.get(1));
-    seq.addDBRef(refs.get(2));
-    seq.addDBRef(refs.get(3));
+    final SequenceI seq0 = seqs.get(0);
+    final SequenceI seq1 = seqs.get(1);
+    final List<SequenceFeature> noFeatures = Collections
+            .<SequenceFeature> emptyList();
+    final String linkText = MessageManager.getString("action.link");
+
+    seq0.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR001041"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR012675"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR006058"));
+    seq1.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
+    seq1.addDBRef(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
     
-    seqs.get(1).addDBRef(refs.get(4));
-    seqs.get(1).addDBRef(refs.get(5));
-    
-    // get the Popup Menu for first sequence
-    List<SequenceFeature> noFeatures = Collections.<SequenceFeature> emptyList();
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
-    Component[] seqItems = testee.sequenceMenu.getMenuComponents();
-    JMenu linkMenu = (JMenu) seqItems[6];
+    /*
+     * check the Link Menu for the first sequence
+     */
+    JMenu linkMenu = PopupMenu.buildLinkMenu(seq0, noFeatures);
+    assertEquals(linkText, linkMenu.getText());
     Component[] linkItems = linkMenu.getMenuComponents();
     
-    // check the number of links are the expected number
+    /*
+     * menu items are ordered: SEQUENCE_ID search first, then dbrefs in order
+     * of database name (and within that by order of dbref addition)
+     */
     assertEquals(5, linkItems.length);
-
-    // first entry is EMBL-EBI which just uses sequence id not accession id?
     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
+    assertEquals("INTERPRO|IPR001041",
+            ((JMenuItem) linkItems[1]).getText());
+    assertEquals("INTERPRO|IPR012675",
+            ((JMenuItem) linkItems[2]).getText());
+    assertEquals("INTERPRO|IPR006058",
+            ((JMenuItem) linkItems[3]).getText());
+    assertEquals("UNIPROT|P83527", ((JMenuItem) linkItems[4]).getText());
 
-    // sequence id for each link should match corresponding DB accession id
-    for (int i = 1; i < 4; i++)
-    {
-      String msg = seq.getName() + " link[" + i + "]";
-      assertEquals(msg, refs.get(i - 1).getSource(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[0]);
-      assertEquals(msg, refs.get(i - 1).getAccessionId(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[1]);
-    }
-
-    // get the Popup Menu for second sequence
-    seq = seqs.get(1);
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
-    seqItems = testee.sequenceMenu.getMenuComponents();
-    linkMenu = (JMenu) seqItems[6];
+    /*
+     * check the Link Menu for the second sequence
+     * note dbref GENE3D is matched to link Gene3D, the latter is displayed
+     */
+    linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
+    assertEquals(linkText, linkMenu.getText());
     linkItems = linkMenu.getMenuComponents();
-    
-    // check the number of links are the expected number
     assertEquals(3, linkItems.length);
-
-    // first entry is EMBL-EBI which just uses sequence id not accession id?
     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
+    assertEquals("Gene3D|3.10.20.30", ((JMenuItem) linkItems[1]).getText());
+    assertEquals("UNIPROT|Q9ZTS2", ((JMenuItem) linkItems[2]).getText());
 
-    // sequence id for each link should match corresponding DB accession id
-    for (int i = 1; i < 3; i++)
-    {
-      String msg = seq.getName() + " link[" + i + "]";
-      assertEquals(msg, refs.get(i + 3).getSource(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[0].toUpperCase());
-      assertEquals(msg, refs.get(i + 3).getAccessionId(),
-              ((JMenuItem) linkItems[i]).getText().split("\\|")[1]);
-    }
-
-    // if there are no valid links the Links submenu is disabled
-    List<String> nomatchlinks = new ArrayList<>();
-    nomatchlinks.add("NOMATCH | http://www.uniprot.org/uniprot/$"
-            + DB_ACCESSION + "$");
-
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
-    seqItems = testee.sequenceMenu.getMenuComponents();
-    linkMenu = (JMenu) seqItems[6];
-    assertFalse(linkMenu.isEnabled());
+    /*
+     * if there are no valid links the Links submenu is still shown, but
+     * reduced to the EMBL-EBI lookup only (inserted by 
+     * CustomUrlProvider.choosePrimaryUrl())
+     */
+    String unmatched = "NOMATCH|http://www.uniprot.org/uniprot/$"
+            + DB_ACCESSION + "$";
+    UrlProviderFactoryI factory = new DesktopUrlProviderFactory(null,
+            unmatched, "");
+    Preferences.sequenceUrlLinks = factory.createUrlProvider();
 
+    linkMenu = PopupMenu.buildLinkMenu(seq1, noFeatures);
+    assertEquals(linkText, linkMenu.getText());
+    linkItems = linkMenu.getMenuComponents();
+    assertEquals(1, linkItems.length);
+    assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
   }
 
-  /**
-   * Test for adding feature links
-   */
   @Test(groups = { "Functional" })
   public void testHideInsertions()
   {
index 91b541c..7d39ea9 100644 (file)
@@ -1,15 +1,25 @@
 package jalview.gui;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.util.MessageManager;
+import jalview.viewmodel.ViewportRanges;
 
 import java.awt.event.MouseEvent;
 
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -28,16 +38,12 @@ public class ScalePanelTest
     SequenceI seq1 = new Sequence("Seq1", "MATRESS");
     SequenceI seq2 = new Sequence("Seq2", "MADNESS");
     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    
+
     AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
             al.getHeight());
-    ScalePanel scalePanel = new ScalePanel(
-            alignFrame.getViewport(), alignFrame.alignPanel
-    );
-    
-    MouseEvent mouse = new MouseEvent(
-            scalePanel, 0, 1, 0, 4, 0, 1, false
-    );
+    ScalePanel scalePanel = alignFrame.alignPanel.getScalePanel();
+
+    MouseEvent mouse = new MouseEvent(scalePanel, 0, 1, 0, 4, 0, 1, false);
     scalePanel.mousePressed(mouse);
     scalePanel.mouseDragged(mouse);
 
@@ -51,8 +57,182 @@ public class ScalePanelTest
     int startCol = sg.getStartRes();
 
     assertTrue(startCol >= 0);
+  }
 
+  /**
+   * Test for JAL-3212
+   */
+  @Test(groups = "Functional")
+  public void testSelectColumns_withHidden()
+  {
+    String seq1 = ">Seq1\nANTOFAGASTAVALPARAISOMONTEVIDEOANTANANARIVO";
+    AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seq1,
+            DataSourceType.PASTE);
+    ScalePanel scalePanel = alignFrame.alignPanel.getScalePanel();
+
+    /*
+     * hide columns 1-20 (of 43); then 'drag' to select columns 30-31;
+     * 31 is 51 in absolute columns but bug JAL-3212 reduces it to
+     * endRes which is 22
+     */
+    AlignViewport viewport = alignFrame.getViewport();
+    ViewportRanges ranges = viewport.getRanges();
+    assertEquals(ranges.getStartRes(), 0);
+    assertEquals(ranges.getEndRes(), 42);
+    viewport.hideColumns(0, 19);
+    alignFrame.alignPanel.updateLayout();
+    assertEquals(ranges.getStartRes(), 0);
+    assertEquals(ranges.getEndRes(), 22);
+    
+    int cw = viewport.getCharWidth();
+    int xPos = 9 * cw + 2;
+    MouseEvent mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1,
+            false);
+    scalePanel.mousePressed(mouse);
+    scalePanel.mouseDragged(mouse);
+    xPos += cw;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseReleased(mouse);
+
+    SequenceGroup sg = scalePanel.av.getSelectionGroup();
+    assertEquals(sg.getStartRes(), 29);
+    assertEquals(sg.getEndRes(), 30);
+  }
+
+  @Test(groups = "Functional")
+  public void testBuildPopupMenu()
+  {
+    final String hide = MessageManager.getString("label.hide_columns");
+    final String reveal = MessageManager.getString("label.reveal");
+    final String revealAll = MessageManager.getString("action.reveal_all");
 
+    String seq1 = ">Seq1\nANTOFAGASTA";
+    AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(seq1,
+            DataSourceType.PASTE);
+    ScalePanel scalePanel = alignFrame.alignPanel.getScalePanel();
+    AlignViewport viewport = alignFrame.getViewport();
+    int cw = viewport.getCharWidth();
+
+    /*
+     * hide columns 3-4 (counting from 0)
+     */
+    viewport.hideColumns(3, 4);
+    alignFrame.alignPanel.updateLayout();
+
+    /*
+     * verify popup menu left/right of hidden column marker
+     * NB need to call mouseMoved first as this sets field 'reveal'
+     */
+    int xPos = 1 * cw + 2; // 2 columns left of hidden marker
+    MouseEvent mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1,
+            false);
+    scalePanel.mouseMoved(mouse);
+    JPopupMenu popup = scalePanel.buildPopupMenu(1);
+    assertEquals(popup.getSubElements().length, 0);
+
+    /*
+     * popup just left of hidden marker has 'Reveal'
+     */
+    xPos = 2 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(2);
+    assertEquals(popup.getSubElements().length, 1);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+
+    /*
+     * popup just right of hidden marker has 'Reveal'
+     */
+    xPos = 3 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(5); // allowing for 2 hidden columns
+    assertEquals(popup.getSubElements().length, 1);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+
+    /*
+     * popup further right is empty
+     */
+    xPos = 4 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(6); // allowing for 2 hidden columns
+    assertEquals(popup.getSubElements().length, 0);
+
+    /*
+     * 'drag' to select columns around the hidden column marker
+     */
+    xPos = 1 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mousePressed(mouse);
+    scalePanel.mouseDragged(mouse);
+    xPos = 5 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseReleased(mouse);
+
+    /*
+     * popup 2 columns left of marker: 'Hide' only
+     */
+    xPos = 1 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(1);
+    assertEquals(popup.getSubElements().length, 1);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), hide);
+
+    /*
+     * popup just left of marker: 'Reveal' and 'Hide'
+     */
+    xPos = 2 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(1);
+    assertEquals(popup.getSubElements().length, 2);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+    assertEquals(((JMenuItem) popup.getSubElements()[1]).getText(), hide);
+
+    /*
+     * popup just right of marker: 'Reveal' and 'Hide'
+     */
+    xPos = 3 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(5);
+    assertEquals(popup.getSubElements().length, 2);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+    assertEquals(((JMenuItem) popup.getSubElements()[1]).getText(), hide);
+
+    /*
+     * hiding a second region adds option 'Reveal all' to 'Reveal'
+     */
+    viewport.hideColumns(6, 7);
+    alignFrame.alignPanel.updateLayout();
+    xPos = 3 * cw + 2;
+    mouse = new MouseEvent(scalePanel, 0, 1, 0, xPos, 0, 1, false);
+    scalePanel.mouseMoved(mouse);
+    popup = scalePanel.buildPopupMenu(5);
+    assertEquals(popup.getSubElements().length, 3);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+    assertEquals(((JMenuItem) popup.getSubElements()[1]).getText(),
+            revealAll);
+    assertEquals(((JMenuItem) popup.getSubElements()[2]).getText(), hide);
+
+    alignFrame.deselectAllSequenceMenuItem_actionPerformed(null);
+    popup = scalePanel.buildPopupMenu(5);
+    assertEquals(popup.getSubElements().length, 2);
+    assertEquals(((JMenuItem) popup.getSubElements()[0]).getText(), reveal);
+    assertEquals(((JMenuItem) popup.getSubElements()[1]).getText(),
+            revealAll);
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public static void setUpBeforeClass() throws Exception
+  {
+    /*
+     * use read-only test properties file
+     */
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    Jalview.main(new String[] { "-nonews" });
   }
 
 }
index 0d49936..cd0b594 100644 (file)
@@ -39,8 +39,8 @@ import jalview.gui.SeqPanel.MousePos;
 import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
 import jalview.util.MessageManager;
+import jalview.viewmodel.ViewportRanges;
 
-import java.awt.Event;
 import java.awt.EventQueue;
 import java.awt.event.MouseEvent;
 import java.lang.reflect.InvocationTargetException;
@@ -241,7 +241,7 @@ public class SeqPanelTest
     /*
      * mouse at top left of unwrapped panel
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
             0, 0, 0, false, 0);
     MousePos pos = testee.findMousePosition(evt);
     assertEquals(pos.column, 0);
@@ -266,8 +266,8 @@ public class SeqPanelTest
     av.setScaleAboveWrapped(false);
     av.setScaleLeftWrapped(false);
     av.setScaleRightWrapped(false);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    waitForSwing(); // for Swing thread
+
+    alignFrame.alignPanel.updateLayout();
 
     final int charHeight = av.getCharHeight();
     final int charWidth = av.getCharWidth();
@@ -286,7 +286,7 @@ public class SeqPanelTest
      * mouse at top left of wrapped panel; there is a gap of charHeight
      * above the alignment
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
             0, 0, 0, false, 0);
     MousePos pos = testee.findMousePosition(evt);
     assertEquals(pos.column, 0);
@@ -297,7 +297,7 @@ public class SeqPanelTest
      * cursor at bottom of gap above
      */
     y = charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -307,7 +307,7 @@ public class SeqPanelTest
      * cursor over top of first sequence
      */
     y = charHeight;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -317,7 +317,7 @@ public class SeqPanelTest
      * cursor at bottom of first sequence
      */
     y = 2 * charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -327,7 +327,7 @@ public class SeqPanelTest
      * cursor at top of second sequence
      */
     y = 2 * charHeight;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 1);
@@ -337,7 +337,7 @@ public class SeqPanelTest
      * cursor at bottom of second sequence
      */
     y = 3 * charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 1);
@@ -347,7 +347,7 @@ public class SeqPanelTest
      * cursor at bottom of last sequence
      */
     y = charHeight * (1 + alignmentHeight) - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -358,7 +358,7 @@ public class SeqPanelTest
      * method reports index of nearest sequence above
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -368,7 +368,7 @@ public class SeqPanelTest
      * cursor still in the gap above annotations, now at the bottom of it
      */
     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -382,7 +382,7 @@ public class SeqPanelTest
        * cursor at the top of the n'th annotation  
        */
       y += 1;
-      evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+      evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
               false, 0);
       pos = testee.findMousePosition(evt);
       assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -392,7 +392,7 @@ public class SeqPanelTest
        * cursor at the bottom of the n'th annotation  
        */
       y += annotationRows[n].height - 1;
-      evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+      evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
               false, 0);
       pos = testee.findMousePosition(evt);
       assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -403,7 +403,7 @@ public class SeqPanelTest
      * cursor in gap between wrapped widths  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -413,7 +413,7 @@ public class SeqPanelTest
      * cursor at bottom of gap between wrapped widths  
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -423,7 +423,7 @@ public class SeqPanelTest
      * cursor at top of first sequence, second wrapped width  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -441,8 +441,7 @@ public class SeqPanelTest
     av.setScaleAboveWrapped(true);
     av.setScaleLeftWrapped(false);
     av.setScaleRightWrapped(false);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    waitForSwing();
+    alignFrame.alignPanel.updateLayout();
 
     final int charHeight = av.getCharHeight();
     final int charWidth = av.getCharWidth();
@@ -461,7 +460,7 @@ public class SeqPanelTest
      * mouse at top left of wrapped panel; there is a gap of charHeight
      * above the alignment
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
             0, 0, 0, false, 0);
     MousePos pos = testee.findMousePosition(evt);
     assertEquals(pos.column, 0);
@@ -473,7 +472,7 @@ public class SeqPanelTest
      * two charHeights including scale panel
      */
     y = 2 * charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -483,7 +482,7 @@ public class SeqPanelTest
      * cursor over top of first sequence
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -493,7 +492,7 @@ public class SeqPanelTest
      * cursor at bottom of first sequence
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -503,7 +502,7 @@ public class SeqPanelTest
      * cursor at top of second sequence
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 1);
@@ -513,7 +512,7 @@ public class SeqPanelTest
      * cursor at bottom of second sequence
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 1);
@@ -524,7 +523,7 @@ public class SeqPanelTest
      * (scale + gap + sequences)
      */
     y = charHeight * (2 + alignmentHeight) - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -534,7 +533,7 @@ public class SeqPanelTest
      * cursor below sequences, in 3-pixel gap above annotations
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -545,7 +544,7 @@ public class SeqPanelTest
      * method reports index of nearest sequence above  
      */
     y += SeqCanvas.SEQS_ANNOTATION_GAP - 1; // 3-1 = 2
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -558,7 +557,7 @@ public class SeqPanelTest
        * cursor at the top of the n'th annotation  
        */
       y += 1;
-      evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+      evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
               false, 0);
       pos = testee.findMousePosition(evt);
       assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -568,7 +567,7 @@ public class SeqPanelTest
        * cursor at the bottom of the n'th annotation  
        */
       y += annotationRows[n].height - 1;
-      evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+      evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
               false, 0);
       pos = testee.findMousePosition(evt);
       assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -579,7 +578,7 @@ public class SeqPanelTest
      * cursor in gap between wrapped widths  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -589,7 +588,7 @@ public class SeqPanelTest
      * cursor at bottom of gap between wrapped widths  
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -599,7 +598,7 @@ public class SeqPanelTest
      * cursor at top of scale, second wrapped width  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -609,7 +608,7 @@ public class SeqPanelTest
      * cursor at bottom of scale, second wrapped width  
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -619,7 +618,7 @@ public class SeqPanelTest
      * cursor at top of first sequence, second wrapped width  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -637,8 +636,7 @@ public class SeqPanelTest
     av.setScaleAboveWrapped(false);
     av.setScaleLeftWrapped(false);
     av.setScaleRightWrapped(false);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    waitForSwing();
+    alignFrame.alignPanel.updateLayout();
 
     final int charHeight = av.getCharHeight();
     final int charWidth = av.getCharWidth();
@@ -657,7 +655,7 @@ public class SeqPanelTest
      * mouse at top left of wrapped panel; there is a gap of charHeight
      * above the alignment
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
             0, 0, 0, false, 0);
     MousePos pos = testee.findMousePosition(evt);
     assertEquals(pos.column, 0);
@@ -668,7 +666,7 @@ public class SeqPanelTest
      * cursor over top of first sequence
      */
     y = charHeight;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -678,7 +676,7 @@ public class SeqPanelTest
      * cursor at bottom of last sequence
      */
     y = charHeight * (1 + alignmentHeight) - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, alignmentHeight - 1);
@@ -688,7 +686,7 @@ public class SeqPanelTest
      * cursor below sequences, at top of charHeight gap between widths
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -698,7 +696,7 @@ public class SeqPanelTest
      * cursor below sequences, at top of charHeight gap between widths
      */
     y += charHeight - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, -1);
@@ -708,7 +706,7 @@ public class SeqPanelTest
      * cursor at the top of the first sequence, second width  
      */
     y += 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, y, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
             false, 0);
     pos = testee.findMousePosition(evt);
     assertEquals(pos.seqIndex, 0);
@@ -725,12 +723,13 @@ public class SeqPanelTest
     int x = 0;
     final int charWidth = alignFrame.getViewport().getCharWidth();
     assertTrue(charWidth > 0); // sanity check
-    assertEquals(alignFrame.getViewport().getRanges().getStartRes(), 0);
+    ViewportRanges ranges = alignFrame.getViewport().getRanges();
+    assertEquals(ranges.getStartRes(), 0);
 
     /*
      * mouse at top left of unwrapped panel
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
             0, 0, 0, false, 0);
     assertEquals(testee.findColumn(evt), 0);
     
@@ -738,7 +737,7 @@ public class SeqPanelTest
      * not quite one charWidth across
      */
     x = charWidth-1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
             0, 0, 0, false, 0);
     assertEquals(testee.findColumn(evt), 0);
 
@@ -746,7 +745,7 @@ public class SeqPanelTest
      * one charWidth across
      */
     x = charWidth;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), 1);
 
@@ -754,7 +753,7 @@ public class SeqPanelTest
      * two charWidths across
      */
     x = 2 * charWidth;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), 2);
 
@@ -762,12 +761,14 @@ public class SeqPanelTest
      * limited to last column of seqcanvas
      */
     x = 20000;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     SeqCanvas seqCanvas = alignFrame.alignPanel.getSeqPanel().seqCanvas;
     int w = seqCanvas.getWidth();
-    // limited to number of whole columns, base 0
-    int expected = w / charWidth - 1;
+    // limited to number of whole columns, base 0,
+    // and to end of visible range
+    int expected = w / charWidth;
+    expected = Math.min(expected, ranges.getEndRes());
     assertEquals(testee.findColumn(evt), expected);
 
     /*
@@ -776,7 +777,7 @@ public class SeqPanelTest
     alignFrame.getViewport().hideColumns(4, 9);
     x = 5 * charWidth + 2;
     // x is in 6th visible column, absolute column 12, or 11 base 0
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), 11);
   }
@@ -791,9 +792,7 @@ public class SeqPanelTest
     av.setScaleAboveWrapped(false);
     av.setScaleLeftWrapped(false);
     av.setScaleRightWrapped(false);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    // need to wait for repaint to finish!
-    waitForSwing();
+    alignFrame.alignPanel.updateLayout();
     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
     int x = 0;
     final int charWidth = av.getCharWidth();
@@ -803,7 +802,7 @@ public class SeqPanelTest
     /*
      * mouse at top left of wrapped panel, no West (left) scale
      */
-    MouseEvent evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
+    MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
             0, 0, 0, false, 0);
     assertEquals(testee.findColumn(evt), 0);
     
@@ -811,7 +810,7 @@ public class SeqPanelTest
      * not quite one charWidth across
      */
     x = charWidth-1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0,
             0, 0, 0, false, 0);
     assertEquals(testee.findColumn(evt), 0);
   
@@ -819,7 +818,7 @@ public class SeqPanelTest
      * one charWidth across
      */
     x = charWidth;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), 1);
 
@@ -827,18 +826,17 @@ public class SeqPanelTest
      * x over scale left (before drawn columns) results in -1
      */
     av.setScaleLeftWrapped(true);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    waitForSwing();
+    alignFrame.alignPanel.updateLayout();
     SeqCanvas seqCanvas = testee.seqCanvas;
     int labelWidth = (int) PA.getValue(seqCanvas, "labelWidthWest");
     assertTrue(labelWidth > 0);
     x = labelWidth - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), -1);
 
     x = labelWidth;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), 0);
 
@@ -848,7 +846,7 @@ public class SeqPanelTest
     int residuesWide = av.getRanges().getViewportWidth();
     assertTrue(residuesWide > 0);
     x = labelWidth + charWidth * residuesWide - 1;
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), residuesWide - 1);
 
@@ -856,15 +854,14 @@ public class SeqPanelTest
      * x over scale right (beyond drawn columns) results in -1
      */
     av.setScaleRightWrapped(true);
-    alignFrame.alignPanel.paintAlignment(false, false);
-    waitForSwing();
+    alignFrame.alignPanel.updateLayout();
     labelWidth = (int) PA.getValue(seqCanvas, "labelWidthEast");
     assertTrue(labelWidth > 0);
     int residuesWide2 = av.getRanges().getViewportWidth();
     assertTrue(residuesWide2 > 0);
     assertTrue(residuesWide2 < residuesWide); // available width reduced
     x += 1; // just over left edge of scale right
-    evt = new MouseEvent(testee, Event.MOUSE_MOVE, 0L, 0, x, 0, 0, 0, 0,
+    evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, 0, 0, 0, 0,
             false, 0);
     assertEquals(testee.findColumn(evt), -1);
     
index 5be7968..cf7f58f 100644 (file)
@@ -89,8 +89,7 @@ public class IdentifyFileTest
   @DataProvider(name = "identifyFiles")
   public Object[][] IdentifyFileDP()
   {
-    return new Object[][] {
-        { "examples/example.json", FileFormat.Json },
+    return new Object[][] { { "examples/example.json", FileFormat.Json },
         { "examples/plantfdx.fa", FileFormat.Fasta },
         { "examples/dna_interleaved.phy", FileFormat.Phylip },
         { "examples/2GIS.pdb", FileFormat.PDB },
@@ -107,13 +106,12 @@ public class IdentifyFileTest
         { "examples/testdata/simpleGff3.gff", FileFormat.Features },
         { "examples/testdata/test.jvp", FileFormat.Jalview },
         { "examples/testdata/test.cif", FileFormat.MMCif },
-        {
-            "examples/testdata/cullpdb_pc25_res3.0_R0.3_d150729_chains9361.fasta.15316",
+        { "examples/testdata/cullpdb_pc25_res3.0_R0.3_d150729_chains9361.fasta.15316",
             FileFormat.Fasta },
         { "resources/scoreModel/pam250.scm", FileFormat.ScoreMatrix },
         { "resources/scoreModel/blosum80.scm", FileFormat.ScoreMatrix }
-    // { "examples/testdata/test.amsa", "AMSA" },
-    // { "examples/test.jnet", "JnetFile" },
+        // { "examples/testdata/test.amsa", "AMSA" },
+        // { "examples/test.jnet", "JnetFile" },
     };
   }
 
@@ -126,8 +124,8 @@ public class IdentifyFileTest
     // too few columns:
     assertFalse(id.looksLikeFeatureData("1 \t 2 \t 3 \t 4 \t 5"));
     // GFF format:
-    assertTrue(id
-            .looksLikeFeatureData("Seq1\tlocal\tHelix\t2456\t2462\tss"));
+    assertTrue(
+            id.looksLikeFeatureData("Seq1\tlocal\tHelix\t2456\t2462\tss"));
     // Jalview format:
     assertTrue(id.looksLikeFeatureData("Helix\tSeq1\t-1\t2456\t2462\tss"));
     // non-numeric start column:
index e86c8ad..ba4312a 100644 (file)
@@ -257,8 +257,8 @@ public class StockholmFileTest
           assertEquals("Threshold line not identical.",
                   aa_original[i].threshold, aa_new[i].threshold);
           // graphGroup may differ, but pattern should be the same
-          Integer o_ggrp = new Integer(aa_original[i].graphGroup + 2);
-          Integer n_ggrp = new Integer(aa_new[i].graphGroup + 2);
+          Integer o_ggrp = Integer.valueOf(aa_original[i].graphGroup + 2);
+          Integer n_ggrp = Integer.valueOf(aa_new[i].graphGroup + 2);
           BitSet orig_g = orig_groups.get(o_ggrp);
           BitSet new_g = new_groups.get(n_ggrp);
           if (orig_g == null)
index 9e0efe6..a87c160 100644 (file)
@@ -75,6 +75,7 @@ public class VCFLoaderTest
     Cache.loadProperties("test/jalview/io/testProps.jvprops");
     Cache.setProperty("VCF_FIELDS", ".*");
     Cache.setProperty("VEP_FIELDS", ".*");
+    Cache.setProperty("VCF_ASSEMBLY", "GRCh38=GRCh38");
     Cache.initLogger();
   }
 
index 307f450..d2c48f1 100644 (file)
  */
 package jalview.util;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import jalview.gui.JvOptionPane;
 
 import java.awt.Button;
-import java.awt.Event;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 
 import org.testng.annotations.BeforeClass;
@@ -51,6 +54,16 @@ public class PlatformTest
   @Test(groups = "Functional")
   public void testIsControlDown_mac()
   {
+    String toolkit = Toolkit.getDefaultToolkit().getClass().getName();
+    if ("sun.awt.X11.XToolkit".equals(toolkit))
+    {
+      /*
+       * this toolkit on the build server fails these tests,
+       * because it returns 2, not 4, for getMenuShortcutKeyMask
+       */
+      return;
+    }
+
     int clickCount = 1;
     boolean isPopupTrigger = false;
     int buttonNo = MouseEvent.BUTTON1;
@@ -61,11 +74,11 @@ public class PlatformTest
     assertFalse(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
 
-    mods = Event.CTRL_MASK;
+    mods = InputEvent.CTRL_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK;
     assertFalse(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
 
-    mods = Event.META_MASK;
+    mods = InputEvent.META_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK;
     assertTrue(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
 
@@ -97,15 +110,35 @@ public class PlatformTest
     assertFalse(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
 
-    mods = Event.CTRL_MASK;
+    mods = InputEvent.CTRL_DOWN_MASK;
     assertTrue(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
 
-    mods = Event.CTRL_MASK | Event.SHIFT_MASK | Event.ALT_MASK;
+    mods = InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK
+            | InputEvent.ALT_DOWN_MASK;
     clickCount = 2;
     buttonNo = 2;
     isPopupTrigger = true;
     assertTrue(Platform.isControlDown(new MouseEvent(b, 0, 0L, mods, 0, 0,
             0, 0, clickCount, isPopupTrigger, buttonNo), mac));
   }
+
+  @Test(groups = "Functional")
+  public void testPathEquals()
+  {
+    assertTrue(Platform.pathEquals(null, null));
+    assertFalse(Platform.pathEquals(null, "apath"));
+    assertFalse(Platform.pathEquals("apath", null));
+    assertFalse(Platform.pathEquals("apath", "APATH"));
+    assertTrue(Platform.pathEquals("apath", "apath"));
+    assertTrue(Platform.pathEquals("apath/a/b", "apath\\a\\b"));
+  }
+
+  @Test(groups = "Functional")
+  public void testEscapeBackslashes()
+  {
+    assertNull(Platform.escapeBackslashes(null));
+    assertEquals(Platform.escapeBackslashes("hello world"), "hello world");
+    assertEquals(Platform.escapeBackslashes("hello\\world"), "hello\\\\world");
+  }
 }
index 4092cf2..c7d37b0 100644 (file)
@@ -33,6 +33,7 @@ import jalview.datamodel.Sequence;
 import jalview.gui.JvOptionPane;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -271,7 +272,7 @@ public class UrlLinkTest
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + SEQUENCE_ID
             + DELIM + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(null, linkset);
 
     String key = DB + SEP + URL_PREFIX;
@@ -291,7 +292,7 @@ public class UrlLinkTest
   {
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(null, linkset);
 
     String key = DB + SEP + URL_PREFIX + URL_SUFFIX;
@@ -311,8 +312,8 @@ public class UrlLinkTest
   {
 
     // create list of links and list of DBRefs
-    List<String> links = new ArrayList<String>();
-    List<DBRefEntry> refs = new ArrayList<DBRefEntry>();
+    List<String> links = new ArrayList<>();
+    List<DBRefEntry> refs = new ArrayList<>();
 
     // links as might be added into Preferences | Connections dialog
     links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
@@ -341,7 +342,7 @@ public class UrlLinkTest
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + SEQUENCE_ID
             + DELIM + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
 
     String key = seq0.getName() + SEP + URL_PREFIX + seq0.getName()
@@ -356,7 +357,7 @@ public class UrlLinkTest
 
     // Test where link takes a db annotation id and only has one dbref
     ul = new UrlLink(links.get(1));
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
 
     key = "P83527|http://www.uniprot.org/uniprot/P83527";
@@ -371,7 +372,7 @@ public class UrlLinkTest
 
     // Test where link takes a db annotation id and has multiple dbrefs
     ul = new UrlLink(links.get(2));
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
     assertEquals(3, linkset.size());
 
@@ -403,7 +404,7 @@ public class UrlLinkTest
     // Test where there are no matching dbrefs for the link
     ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + DB_ACCESSION + DELIM
             + URL_SUFFIX);
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
     assertTrue(linkset.isEmpty());
   }
@@ -439,4 +440,25 @@ public class UrlLinkTest
 
   }
 
+  @Test(groups = { "Functional" })
+  public void testLinkComparator()
+  {
+    Comparator<String> c = UrlLink.LINK_COMPARATOR;
+    assertEquals(0, c.compare(null, null));
+    assertEquals(0, c.compare(null, "x"));
+    assertEquals(0, c.compare("y", null));
+
+    /*
+     * SEQUENCE_ID templates should come before DB_ACCESSION templates
+     */
+    String dbRefUrl = "Cath|http://www.cathdb.info/version/v4_2_0/superfamily/$DB_ACCESSION$";
+    String seqIdUrl = "EBI|https://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$";
+    assertTrue(c.compare(seqIdUrl, dbRefUrl) < 0);
+    assertTrue(c.compare(dbRefUrl, seqIdUrl) > 0);
+
+    String interpro = "Interpro|https://www.ebi.ac.uk/interpro/entry/$DB_ACCESSION$";
+    String prosite = "ProSite|https://prosite.expasy.org/PS00197";
+    assertTrue(c.compare(interpro, prosite) < 0);
+    assertTrue(c.compare(prosite, interpro) > 0);
+  }
 }
index c69d554..3afa211 100755 (executable)
@@ -1280,7 +1280,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="b1a16838a449">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="b1a16839a449">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1297,7 +1297,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[castor-1.1-cycle-xml.jar]]></string>
+                                                               <string><![CDATA[vamsas-client.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1315,10 +1315,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[castor-1.1-cycle-xml.jar]]></string>
+                                                               <string><![CDATA[vamsas-client.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>1035998</long>
+                                                               <long>682375</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1332,7 +1332,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="b1a16839a449">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="495aeddb8b3d">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1349,7 +1349,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[vamsas-client.jar]]></string>
+                                                               <string><![CDATA[JGoogleAnalytics_0.3.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1367,10 +1367,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[vamsas-client.jar]]></string>
+                                                               <string><![CDATA[JGoogleAnalytics_0.3.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>682375</long>
+                                                               <long>8300</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1384,7 +1384,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="495aeddb8b3d">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="4996716cba8e">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1401,7 +1401,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[JGoogleAnalytics_0.3.jar]]></string>
+                                                               <string><![CDATA[jabaws-min-client-2.2.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1419,10 +1419,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[JGoogleAnalytics_0.3.jar]]></string>
+                                                               <string><![CDATA[jabaws-min-client-2.2.0.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>8300</long>
+                                                               <long>601804</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1436,7 +1436,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="4996716cba8e">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17529b9c">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1453,7 +1453,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jabaws-min-client-2.2.0.jar]]></string>
+                                                               <string><![CDATA[httpcore-4.0.1.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1471,10 +1471,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[min-jabaws-client-2.2.0.jar]]></string>
+                                                               <string><![CDATA[httpcore-4.0.1.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>601804</long>
+                                                               <long>172888</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1488,7 +1488,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17529b9c">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17519b9c">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1505,7 +1505,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[httpcore-4.0.1.jar]]></string>
+                                                               <string><![CDATA[httpclient-4.0.3.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1523,10 +1523,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[httpcore-4.0.1.jar]]></string>
+                                                               <string><![CDATA[httpclient-4.0.3.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>172888</long>
+                                                               <long>292893</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1540,7 +1540,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17519b9c">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17539b9c">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1557,7 +1557,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[httpclient-4.0.3.jar]]></string>
+                                                               <string><![CDATA[httpmime-4.0.3.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1575,10 +1575,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[httpclient-4.0.3.jar]]></string>
+                                                               <string><![CDATA[httpmime-4.0.3.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>292893</long>
+                                                               <long>25447</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1592,7 +1592,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17539b9c">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17529b9d">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1609,7 +1609,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[httpmime-4.0.3.jar]]></string>
+                                                               <string><![CDATA[apache-mime4j-0.6.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1627,10 +1627,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[httpmime-4.0.3.jar]]></string>
+                                                               <string><![CDATA[apache-mime4j-0.6.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>25447</long>
+                                                               <long>345048</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1644,7 +1644,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="8fbb17529b9d">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a521fb6bc">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1661,7 +1661,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[apache-mime4j-0.6.jar]]></string>
+                                                               <string><![CDATA[miglayout-4.0-swing.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1679,10 +1679,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[apache-mime4j-0.6.jar]]></string>
+                                                               <string><![CDATA[miglayout-4.0-swing.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>345048</long>
+                                                               <long>77291</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1696,7 +1696,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a521fb6bc">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a5220b6bc1">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1713,7 +1713,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[miglayout-4.0-swing.jar]]></string>
+                                                               <string><![CDATA[commons-codec-1.3.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1731,10 +1731,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[miglayout-4.0-swing.jar]]></string>
+                                                               <string><![CDATA[commons-codec-1.3.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>77291</long>
+                                                               <long>46725</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1748,7 +1748,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a5220b6bc1">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a5220b6bc">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1765,7 +1765,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[commons-codec-1.3.jar]]></string>
+                                                               <string><![CDATA[jswingreader-0.3.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1783,10 +1783,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[commons-codec-1.3.jar]]></string>
+                                                               <string><![CDATA[jswingreader-0.3.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>46725</long>
+                                                               <long>86618</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1800,7 +1800,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="aa3a5220b6bc">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="df3a5220b6bc">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1817,7 +1817,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jswingreader-0.3.jar]]></string>
+                                                               <string><![CDATA[VARNAv3-93.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1835,10 +1835,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jswingreader-0.3.jar]]></string>
+                                                               <string><![CDATA[VARNAv3-93.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>86618</long>
+                                                               <long>695802</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1852,7 +1852,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="df3a5220b6bc">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="f44ca393ab9f">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1869,7 +1869,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[VARNAv3-93.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1887,10 +1887,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[VARNAv3-93.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>695802</long>
+                                                               <long>6149494</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1904,7 +1904,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="f44ca391ab9f">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1f46cffffab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1921,7 +1921,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[spring-web-3.0.5.RELEASE.jar]]></string>
+                                                               <string><![CDATA[jfreesvg-2.1.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1939,10 +1939,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[spring-web-3.0.5.RELEASE.jar]]></string>
+                                                               <string><![CDATA[jfreesvg-2.1.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>395587</long>
+                                                               <long>49768</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -1956,7 +1956,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="f44ca392ab9f">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1f46efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -1973,7 +1973,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jdas-1.0.4.jar]]></string>
+                                                               <string><![CDATA[json_simple-1.1.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -1991,10 +1991,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jdas-1.0.4.jar]]></string>
+                                                               <string><![CDATA[json_simple-1.1.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>249906</long>
+                                                               <long>16046</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2008,7 +2008,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="f44ca393ab9f">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="841f46efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2025,7 +2025,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
+                                                               <string><![CDATA[libquaqua64-8.0.jnilib.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2043,10 +2043,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[groovy-all-2.4.12-indy.jar]]></string>
+                                                               <string><![CDATA[libquaqua64-8.0.jnilib.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>6149494</long>
+                                                               <long>16046</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2060,7 +2060,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="f46c2f42ab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="9a1f46efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2077,7 +2077,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[spring-core-3.0.5.RELEASE.jar]]></string>
+                                                               <string><![CDATA[libquaqua-8.0.jnilib.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2095,10 +2095,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[spring-core-3.0.5.RELEASE.jar]]></string>
+                                                               <string><![CDATA[libquaqua-8.0.jnilib.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>382442</long>
+                                                               <long>16046</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2111,8 +2111,61 @@ and any path to a file to save to the file]]></string>
                                                        </property>
                                                </object>
                                        </method>
+
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1f46cffffab93">
+            <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="9a1f46efeef911a">
+              <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[VAqua5-patch.jar]]></string>
+              </property>
+              <property name="overrideUnixPermissions">
+                <boolean>false</boolean>
+              </property>
+              <property name="sourcePath">
+                <string><![CDATA[/homes/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[VAqua5-patch.jar]]></string>
+              </property>
+              <property name="fileSize">
+                <long>1370564</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">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2129,7 +2182,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jfreesvg-2.1.jar]]></string>
+                                                               <string><![CDATA[quaqua-filechooser-only-8.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2147,10 +2200,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jfreesvg-2.1.jar]]></string>
+                                                               <string><![CDATA[quaqua-filechooser-only-8.0.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>49768</long>
+                                                               <long>660179</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2164,7 +2217,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1f46efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="10936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2181,7 +2234,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[json_simple-1.1.jar]]></string>
+                                                               <string><![CDATA[jersey-client-1.19.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2199,10 +2252,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[json_simple-1.1.jar]]></string>
+                                                               <string><![CDATA[jersey-client-1.19.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>16046</long>
+                                                               <long>134021</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2216,7 +2269,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="841f46efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="11936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2233,7 +2286,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[libqaqua64.jnilib]]></string>
+                                                               <string><![CDATA[jersey-core-1.19.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2251,10 +2304,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[libqaqua64.jnilib]]></string>
+                                                               <string><![CDATA[jersey-core-1.19.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>16046</long>
+                                                               <long>436689</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2268,7 +2321,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="9a1f46efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="12936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2285,7 +2338,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[libquaqua.jnilib]]></string>
+                                                               <string><![CDATA[jersey-json-1.19.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2303,10 +2356,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[libquaqua.jnilib]]></string>
+                                                               <string><![CDATA[jersey-json-1.19.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>16046</long>
+                                                               <long>165345</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2320,59 +2373,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-            <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="9a1f46efeef911a">
-              <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[VAqua5-patch.jar]]></string>
-              </property>
-              <property name="overrideUnixPermissions">
-                <boolean>false</boolean>
-              </property>
-              <property name="sourcePath">
-                <string><![CDATA[/homes/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[VAqua5-patch.jar]]></string>
-              </property>
-              <property name="fileSize">
-                <long>1370564</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">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="13936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2389,7 +2390,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[quaqua-filechooser-only-8.0.jar]]></string>
+                                                               <string><![CDATA[java-json.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2407,10 +2408,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[quaqua-filechooser-only-8.0.jar]]></string>
+                                                               <string><![CDATA[java-json.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>660179</long>
+                                                               <long>84697</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2424,7 +2425,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="10936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="14936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2441,7 +2442,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jersey-client-1.19.jar]]></string>
+                                                               <string><![CDATA[jsr311-api-1.1.1.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2459,10 +2460,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jersey-client-1.19.jar]]></string>
+                                                               <string><![CDATA[jsr311-api-1.1.1.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>134021</long>
+                                                               <long>46367</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2476,7 +2477,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="11936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="15936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2493,7 +2494,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jersey-core-1.19.jar]]></string>
+                                                               <string><![CDATA[jetty-http-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2511,10 +2512,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jersey-core-1.19.jar]]></string>
+                                                               <string><![CDATA[jetty-http-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>436689</long>
+                                                               <long>105748</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2528,7 +2529,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="12936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="16936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2545,7 +2546,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jersey-json-1.19.jar]]></string>
+                                                               <string><![CDATA[jetty-io-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2563,10 +2564,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jersey-json-1.19.jar]]></string>
+                                                               <string><![CDATA[jetty-io-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>165345</long>
+                                                               <long>108132</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2580,7 +2581,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="13936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="17936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2597,7 +2598,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[java-json.jar]]></string>
+                                                               <string><![CDATA[jetty-server-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2615,10 +2616,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[java-json.jar]]></string>
+                                                               <string><![CDATA[jetty-server-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>84697</long>
+                                                               <long>418917</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2632,7 +2633,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="14936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="18936efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2649,7 +2650,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jsr311-api-1.1.1.jar]]></string>
+                                                               <string><![CDATA[jetty-util-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2667,10 +2668,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jsr311-api-1.1.1.jar]]></string>
+                                                               <string><![CDATA[jetty-util-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>46367</long>
+                                                               <long>356253</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2684,7 +2685,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="15936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1899e36efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2701,7 +2702,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jetty-http-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[jetty-servlet-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2719,10 +2720,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jetty-http-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[jetty-servlet-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>105748</long>
+                                                               <long>356253</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2736,7 +2737,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="16936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1899d6efeefab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2753,7 +2754,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jetty-io-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[jetty-security-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2771,10 +2772,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jetty-io-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[jetty-security-9.2.10.v20150310.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>108132</long>
+                                                               <long>356253</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2788,7 +2789,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="17936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1000ddddfab93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2805,7 +2806,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jetty-server-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[htsjdk-2.12.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2823,10 +2824,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jetty-server-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[htsjdk-2.12.0.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>418917</long>
+                                                               <long>16046</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2840,7 +2841,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="18936efeefab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1000ddddfb93">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2857,7 +2858,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[jetty-util-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[intervalstore-v1.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2875,7 +2876,59 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[jetty-util-9.2.10.v20150310.jar]]></string>
+                                                               <string><![CDATA[intervalstore-v1.0.jar]]></string>
+                                                       </property>
+                                                       <property name="fileSize">
+                                                               <long>26303</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">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="b10a3">
+                                                       <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[biojava-core-4.1.0.jar]]></string>
+                                                       </property>
+                                                       <property name="overrideUnixPermissions">
+                                                               <boolean>false</boolean>
+                                                       </property>
+                                                       <property name="sourcePath">
+                                                               <string><![CDATA[/homes/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[biojava-core-4.1.0.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
                                                                <long>356253</long>
@@ -2892,7 +2945,7 @@ and any path to a file to save to the file]]></string>
                                                </object>
                                        </method>
                                        <method name="addElement">
-                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1000ddddfab93">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="b10a30">
                                                        <property name="belongsToUninstallPhase">
                                                                <boolean>false</boolean>
                                                        </property>
@@ -2909,7 +2962,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[htsjdk-1.133.jar]]></string>
+                                                               <string><![CDATA[biojava-ontology-4.1.0.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2927,10 +2980,10 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[htsjdk-1.133.jar]]></string>
+                                                               <string><![CDATA[biojava-ontology-4.1.0.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
-                                                               <long>16046</long>
+                                                               <long>356253</long>
                                                        </property>
                                                        <property name="macBinary">
                                                                <boolean>false</boolean>
@@ -2943,6 +2996,7 @@ and any path to a file to save to the file]]></string>
                                                        </property>
                                                </object>
                                        </method>
+
                                        <method name="addElement">
                                                <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="1000ddddfab939">
                                                        <property name="belongsToUninstallPhase">
@@ -7445,7 +7499,6 @@ and any path to a file to read from that file]]></string>
                                                                                        </property>
                                                                                </object>
                                                                                <object refID="88d4aff3b0c6"/>
-                                                                               <object refID="b1a16838a449"/>
                                                                                <object refID="b1a16839a449"/>
                                                                                <object refID="495aeddb8b3d"/>
                                                                                <object refID="4996716cba8e"/>
@@ -7457,14 +7510,14 @@ and any path to a file to read from that file]]></string>
                                                                                <object refID="aa3a5220b6bc"/>
                                                                                <object refID="df3a5220b6bc"/>
                                                                                <object refID="aa3a5220b6bc1"/>
-                                                                               <object refID="f44ca391ab9f"/>
-                                                                               <object refID="f44ca392ab9f"/>
+
                                                                                <object refID="f44ca393ab9f"/>
-                                                                               <object refID="f46c2f42ab93"/>
+
                                                                                <object refID="1f46cffffab93"/>
                                                                                <object refID="1f46efeefab93"/>
                                                                                <object refID="1936efeefab93"/>
                                                                                <object refID="1000ddddfab93"/>
+                                                                               <object refID="1000ddddfb93"/>
                                                                                <object refID="1000ddddfab939"/>
                                                                                <object refID="10936efeefab93"/>
                                                                                <object refID="11936efeefab93"/>
@@ -7475,6 +7528,11 @@ and any path to a file to read from that file]]></string>
                                                                                <object refID="16936efeefab93"/>
                                                                                <object refID="17936efeefab93"/>
                                                                                <object refID="18936efeefab93"/>
+                                                                               <object refID="b10a3"/>
+                                                                               <object refID="b10a30"/>
+                                                                               <object refID="1899e36efeefab93"/>
+                                                                               <object refID="1899d6efeefab93"/>
+
                                                                                <object class="com.zerog.ia.installer.actions.InstallFile" objectID="f44fc5b2aba1">
                                                                                        <property name="belongsToUninstallPhase">
                                                                                                <boolean>false</boolean>
diff --git a/utils/de-multi-release-jar.sh b/utils/de-multi-release-jar.sh
new file mode 100755 (executable)
index 0000000..6a58c23
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+CMD=$(basename $0)
+
+usage() {
+  echo "Usage: $CMD [-v N] /path/to/jarfile" >&2
+  echo "             -v N   build jar with alternative classes for version N of java (optional, defaults to base jar packages which is usually 8)" >&2
+}
+
+usagexit() {
+  usage
+  exit 1
+}
+
+error() {
+  echo $1 >&2
+  usagexit
+}
+
+VERSION=""
+while getopts v: opt
+do
+  case "${opt}"
+    in
+    v) VERSION=${OPTARG};;
+  esac
+done
+shift $((OPTIND-1))
+
+JARFILE=$1
+[ -z $JARFILE ] && usagexit
+[ -f $JARFILE ] || error "No file $JARFILE"
+[ -r $JARFILE ] || error "$JARFILE not readable"
+
+EXT=.jar
+SUFFIX=-SINGLE_RELEASE
+FILENAME=$(basename $JARFILE)
+BASE=$(basename -s $EXT $JARFILE)
+DIR=$(dirname $JARFILE)
+
+# set absolute path to $JARFILE if not specified
+[ x${DIR#/} = x$DIR ] && DIR=$(cd "$DIR" && pwd)
+
+TMPDIR=/tmp/$USER-$CMD-$BASE-$$
+
+[ x$FILENAME = x$BASE ] && error "Should be $EXT file"
+
+mkdir -p $TMPDIR || error "Could not create tmp dir $TMPDIR"
+cd $TMPDIR
+jar -xvf $JARFILE > /dev/null
+VDIR=$TMPDIR/META-INF/versions
+
+[ -d $VDIR ] || error "$JARFILE doesn't look like a multi-release jar file"
+
+if [ -z $VERSION ]; then
+  # no version set... nothing to copy
+  echo ""
+elif [ -d $VDIR/$VERSION ]; then
+  # this version has alternative classes for the version asked for, copy them into the base jar
+  tar -cf - -C $VDIR/$VERSION . | tar -xf - -C $TMPDIR
+else
+  echo "No specific classes for version $VERSION" >&2
+  echo "Available alternative versions are" >&2
+  cd $VDIR
+  ls >&2
+fi
+
+# remove the alternative versions
+/bin/rm -r $VDIR
+
+# alter the manifest. note sed on macos is a bit weird
+if [ `uname -s` = "Darwin" ]; then
+  sed -E -i '' 's/^([Mm]ulti-[Rr]elease):).*/\1 false/' $TMPDIR/META-INF/MANIFEST.MF
+else
+  sed -E -i 's/^([Mm]ulti-[Rr]elease):).*/\1 false/' $TMPDIR/META-INF/MANIFEST.MF
+fi
+
+jar -cvf $DIR/${BASE}${SUFFIX}${EXT} -C $TMPDIR . > /dev/null
+rm -rf $TMPDIR
+
diff --git a/utils/deprecation_auto_fixes.sh b/utils/deprecation_auto_fixes.sh
new file mode 100644 (file)
index 0000000..27a6319
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+TYPES="Boolean|Character|Double|Float|Long|Integer" find src test -type f -name "*.java" -exec perl -p -i -e 's/\bnew\s+(java\.lang\.)?($ENV{TYPES})\(/${1}${2}.valueOf(/;' {} +
+
+find src test -type f -name "*.java" -exec perl -p -i -e 's/(InputEvent|KeyEvent)\s*\.\s*([A-Z0-9]+)_MASK\b/${1}.${2}_DOWN_MASK/g;' {} +
+
+find src test -type f -name "*.java" -exec perl -p -i -e 's/\b(e|evt)\.getModifiers\b/${1}.getModifiersEx/g;' {} +
+
+find src test -type f -name "*.java" -exec perl -p -i -e 's/\.getMenuShortcutKeyMask\b/\.getMenuShortcutKeyMaskEx/g;' {} +
+
+#
+find src test -type f -name "*.java" -exec perl -p -i -e 'if ( s/^\s*import\s+java\.awt\.Event\s*;\s*$/import java.awt.event.InputEvent;/ ) { $event = 1 }; if ($event == 1) { s/\b(java\.awt\.)?Event\s*\.\s*([A-Z0-9]+)_MASK\b/InputEvent.${2}_DOWN_MASK/g; s/\b(java\.awt\.)?Event\s*\.\s*(MOUSE_MOVE)/MouseEvent.MOUSE_MOVED/g }' {} +
diff --git a/utils/dev_macos_install.sh b/utils/dev_macos_install.sh
new file mode 100755 (executable)
index 0000000..5eadf0d
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+# perform a dev build and install on local macOS machine
+INSTALLERVOL="Jalview Installer"
+APP=Jalview.app
+
+APPLICATIONS=/Applications
+CHANNEL=NOCHANNEL
+DMG=build/install4j/1.8/Jalview-OFFLINE_macos-app_DEVELOPMENT-j8.dmg
+
+if [ $1 != "nogradle" ]; then
+  gradle installers -Pgetdown_channel_name=NOCHANNEL -Pinstall4jMediaTypes=macosArchive -Pgetdown_local=true -Pdev=true
+else
+  echo "Not running gradle installers"
+fi
+
+if [ $? = 0 ]; then
+  umount "/Volumes/$INSTALLERVOL"
+  if [ -e "$DMG" ]; then
+    open $DMG
+  else
+    echo "No DMG file '$DMG'" 1>&2
+    exit 1
+  fi
+  echo "Mounting '$DMG' at /Volumes"
+  N=0
+  while [ \! -e "/Volumes/$INSTALLERVOL/$APP" ]; do
+    if [ $(( N%1000 )) = 0 ]; then
+      echo -n "."
+    fi
+    N=$(( N+1 ))
+  done
+  echo ""
+fi
+if [ -e "/Volumes/$INSTALLERVOL/$APP" ]; then
+  /bin/rm -r "$APPLICATIONS/$APP"
+  rsync -avh "/Volumes/$INSTALLERVOL/$APP" "$APPLICATIONS/"
+  umount "/Volumes/$INSTALLERVOL"
+fi
index 8496f8b..8e09423 100644 (file)
@@ -139,7 +139,7 @@ public class getJavaVersion
     if (versions == null)
     {
       versions = new Hashtable();
-      versions.put("45.3", "1.0");
+      versions.put("45.3", "1.0.2");
       versions.put("45.3", "1.1");
       versions.put("46.0", "1.2");
       versions.put("47.0", "1.3");
@@ -148,6 +148,9 @@ public class getJavaVersion
       versions.put("50.0", "1.6");
       versions.put("51.0", "1.7");
       versions.put("52.0", "1.8");
+      versions.put("53.0", "9");
+      versions.put("54.0", "10");
+      versions.put("55.0", "11");
 
     }
     String version = (String) versions.get(major + "." + minor);
diff --git a/utils/install4j/DS_Store b/utils/install4j/DS_Store
new file mode 100644 (file)
index 0000000..374da46
Binary files /dev/null and b/utils/install4j/DS_Store differ
diff --git a/utils/install4j/Info_plist_file_associations.xml b/utils/install4j/Info_plist_file_associations.xml
new file mode 100644 (file)
index 0000000..bcf4783
--- /dev/null
@@ -0,0 +1,39 @@
+<key>CFBundleDocumentTypes</key>
+<array>
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>jvp</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview Project File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-project</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>jvl</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview Version Locator</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-Version-Locator.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-version-locator</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+</array>
diff --git a/utils/install4j/Jalview-File.icns b/utils/install4j/Jalview-File.icns
new file mode 100644 (file)
index 0000000..a589f05
Binary files /dev/null and b/utils/install4j/Jalview-File.icns differ
diff --git a/utils/install4j/Jalview-File.ico b/utils/install4j/Jalview-File.ico
new file mode 100644 (file)
index 0000000..694d180
Binary files /dev/null and b/utils/install4j/Jalview-File.ico differ
diff --git a/utils/install4j/Jalview-Version-Locator.icns b/utils/install4j/Jalview-Version-Locator.icns
new file mode 100644 (file)
index 0000000..506b5a1
Binary files /dev/null and b/utils/install4j/Jalview-Version-Locator.icns differ
diff --git a/utils/install4j/Jalview-Version-Locator.ico b/utils/install4j/Jalview-Version-Locator.ico
new file mode 100644 (file)
index 0000000..4a2545b
Binary files /dev/null and b/utils/install4j/Jalview-Version-Locator.ico differ
diff --git a/utils/install4j/Jalview-Version-Locator.png b/utils/install4j/Jalview-Version-Locator.png
new file mode 100644 (file)
index 0000000..cb2ce97
Binary files /dev/null and b/utils/install4j/Jalview-Version-Locator.png differ
diff --git a/utils/install4j/JalviewVersionLocator.png b/utils/install4j/JalviewVersionLocator.png
new file mode 100644 (file)
index 0000000..9441080
Binary files /dev/null and b/utils/install4j/JalviewVersionLocator.png differ
diff --git a/utils/install4j/auto_file_associations.pl b/utils/install4j/auto_file_associations.pl
new file mode 100755 (executable)
index 0000000..e59044f
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env perl
+
+use strict;
+
+my $fileformats = $ARGV[0];
+$fileformats = "../../src/jalview/io/FileFormat.java" unless $fileformats;
+
+# default mimetype will be text/x-$shortname
+# TODO: find an actual extension for mat, see JAL-Xxxxx for outstanding issues too
+# TODO: look up standard mime type used for BLASTfmt matrices, etc
+my $mimetypes = {
+  rnaml => "application/rnaml+xml",
+  biojson => "application/x-jalview-biojson+json",
+  jnet => "application/x-jalview-jnet+text",
+  features => "application/x-jalview-features+text",
+  scorematrix => "application/x-jalview-scorematrix+text",
+  pdb => "chemical/x-pdb",
+  mmcif => "chemical/x-cif",
+  mmcif2 => "chemical/x-mcif",
+  jalview => "application/x-jalview+xml+zip",
+  jvl => "application/x-jalview-jvl+text",
+  annotations => "application/x-jalview-annotations+text",
+};
+
+my @dontaddshortname = qw(features json);
+my @dontaddextension = qw(html xml json jar mfa fastq);
+my $add_associations = {
+  biojson => {shortname=>"biojson",name=>"BioJSON",extensions=>["biojson"]},
+  gff2 => {shortname=>"gff2",name=>"Generic Features Format v2",extensions=>["gff2"]},
+  gff3 => {shortname=>"gff3",name=>"Generic Features Format v3",extensions=>["gff3"]},
+  features => {shortname=>"features",name=>"Jalview Features",extensions=>["features","jvfeatures"]},
+  annotations => {shortname=>"annotations",name=>"Jalview Annotations",extensions=>["annotations","jvannotations"]},
+  mmcif2 => {shortname=>"mmcif2",name=>"mmCIF",extensions=>["mcif","mmcif"]},
+  jvl => {shortname=>"jvl",name=>"Jalview Version Locator",extensions=>["jvl"],iconfile=>"Jalview-Version-Locator"},
+  jnet => {shortname=>"jnet",name=>"JnetFile",extensions=>["concise","jnet"]},
+  scorematrix => {shortname=>"scorematrix",name=>"Substitution Matrix",extensions=>["mat"]},
+};
+my $add_extensions = {
+  blc => ["blc"],
+};
+my @put_first = qw(jalview jvl);
+
+my $mactemplatefile = "file_associations_template-Info_plist.xml";
+my $i4jtemplatefile = "file_associations_template-install4j.xml";
+my $mactemplate;
+my $i4jtemplate;
+open(MT,"<$mactemplatefile") or dir("Could not open '$mactemplatefile' for reading");
+while(<MT>){
+  $mactemplate .= $_;
+}
+close(MT);
+open(IT,"<$i4jtemplatefile") or dir("Could not open '$i4jtemplatefile' for reading");
+while(<IT>){
+  $i4jtemplate .= $_;
+}
+close(IT);
+my $macauto;
+my $i4jauto;
+
+my $macautofile = $mactemplatefile;
+$macautofile =~ s/template/auto$1/;
+my $i4jautofile = $i4jtemplatefile;
+$i4jautofile =~ s/template/auto$1/;
+
+open(MA,">$macautofile") or die ("Could not open '$macautofile' for writing");
+print MA "<key>CFBundleDocumentTypes</key>\n<array>\n\n";
+open(IA,">$i4jautofile") or die ("Could not open '$i4jautofile' for writing");
+
+open(IN, "<$fileformats") or die ("Could not open '$fileformats' for reading");
+my $id = 10000;
+my $file_associations = {};
+while(my $line = <IN>) {
+  $line =~ s/\s+/ /g;
+  $line =~ s/(^ | $)//g;
+  if ($line =~ m/^(\w+) ?\( ?"([^"]*)" ?, ?"([^"]*)" ?, ?(true|false) ?, ?(true|false) ?\)$/i) {
+    my $shortname = lc($1);
+    next if (grep($_ eq $shortname, @dontaddshortname));
+    my $name = $2;
+    my $extensions = $3;
+    $extensions =~ s/\s+//g;
+    my @possextensions = split(m/,/,$extensions);
+    my @extensions;
+    my $addext = $add_extensions->{$shortname};
+    if (ref($addext) eq "ARRAY") {
+      push(@possextensions, @$addext);
+    }
+    for my $possext (@possextensions) {
+      next if grep($_ eq $possext, @extensions);
+      next if grep($_ eq $possext, @dontaddextension);
+      push(@extensions,$possext);
+    }
+    next unless scalar(@extensions);
+    $file_associations->{$shortname} = {
+      shortname => $shortname,
+      name => $name,
+      extensions => \@extensions
+    };
+    warn("Adding file association for $shortname\n");
+  }
+}
+close(IN);
+
+my %all_associations = (%$file_associations, %$add_associations);
+
+for my $shortname (@put_first, sort keys %all_associations) {
+  my $a = $all_associations{$shortname};
+  if (ref($a) ne "HASH") {
+    next;
+  }
+
+  my $name = $a->{name};
+  my $extensions = $a->{extensions};
+  my $mimetype = $mimetypes->{$shortname};
+  $mimetype = "text/x-$shortname" unless $mimetype;
+
+  my $iconfile = $a->{iconfile};
+  $iconfile = "Jalview-File" unless $iconfile;
+
+  my @extensions = @$extensions;
+  #warn("LINE: $line\nFound extensions '".join("', '", @extensions)."' for $name Files ($shortname)'n");
+
+  my $macentry = $mactemplate;
+  my $i4jentry = $i4jtemplate;
+
+  $macentry =~ s/\$\$NAME\$\$/$name/g;
+  $macentry =~ s/\$\$SHORTNAME\$\$/$shortname/g;
+  $macentry =~ s/\$\$MIMETYPE\$\$/$mimetype/g;
+  $macentry =~ s/\$\$ICONFILE\$\$/$iconfile/g;
+
+  $i4jentry =~ s/\$\$NAME\$\$/$name/g;
+  $i4jentry =~ s/\$\$SHORTNAME\$\$/$shortname/g;
+  $i4jentry =~ s/\$\$MIMETYPE\$\$/$mimetype/g;
+  $i4jentry =~ s/\$\$ICONFILE\$\$/$iconfile/g;
+
+  while ($macentry =~ m/\$\$([^\$]*)EXTENSIONS([^\$]*)\$\$/) {
+    my $pre = $1;
+    my $post = $2;
+    my $macextensions;
+    for my $ext (@extensions) {
+      $macextensions .= $pre.$ext.$post;
+    }
+    $macentry =~ s/\$\$${pre}EXTENSIONS${post}\$\$/$macextensions/g;
+  }
+  print MA $macentry;
+
+  for my $ext (@extensions) {
+    my $i4jextentry = $i4jentry;
+    $i4jextentry =~ s/\$\$EXTENSION\$\$/$ext/g;
+    $i4jextentry =~ s/\$\$ID\$\$/$id/g;
+    $id++;
+
+    print IA $i4jextentry;
+  }
+
+  delete $all_associations{$shortname};
+}
+
+close(IA);
+print MA "</array>\n";
+close(MA);
diff --git a/utils/install4j/bs_install4j_template.install4j b/utils/install4j/bs_install4j_template.install4j
new file mode 100644 (file)
index 0000000..b76d779
--- /dev/null
@@ -0,0 +1,1862 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<install4j version="7.0.9" transformSequenceNumber="7">
+  <directoryPresets config="../../../../../buildtools/jre/openjdk-java_vm/macos-jdk8u202-b08-jre" />
+  <application name="Jalview" distributionSourceDir="" applicationId="6595-2347-1923-0725" mediaDir="../../build/install4j/$$JAVA_VERSION$$" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.platform}_${compiler:sys.version}" compression="6" lzmaCompression="true" pack200Compression="false" excludeSignedFromPacking="true" commonExternalFiles="false" createMd5Sums="true" shrinkRuntime="true" shortName="Jalview" publisher="University of Dundee" publisherWeb="http://www.jalview.org/" version="$$VERSION$$" allPathsRelative="true" backupOnSave="false" autoSave="false" convertDotsToUnderscores="true" macSignature="????" macVolumeId="5aac4968c304f65" javaMinVersion="9999999999" javaMaxVersion="" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
+    <languages skipLanguageSelection="false" languageSelectionInPrincipalLanguage="false">
+      <principalLanguage id="en" customLocalizationFile="" />
+      <additionalLanguages />
+    </languages>
+    <searchSequence>
+      <directory location="jre" />
+    </searchSequence>
+    <variables>
+      <variable name="OSX_KEYSTORE" value="" description="" category="" />
+      <variable name="JSIGN_SH" value="" description="" category="" />
+    </variables>
+    <mergedProjects />
+    <codeSigning macEnabled="true" macPkcs12File="${compiler:OSX_KEYSTORE}" windowsEnabled="false" windowsKeySource="pkcs12" windowsPvkFile="" windowsSpcFile="" windowsPkcs12File="" windowsPkcs11Library="" windowsPkcs11Slot="">
+      <windowsKeystoreIdentifier issuer="" serial="" subject="" />
+      <windowsPkcs11Identifier issuer="" serial="" subject="" />
+    </codeSigning>
+  </application>
+  <files keepModificationTimes="false" missingFilesStrategy="warn" globalExcludeSuffixes="" defaultOverwriteMode="4" defaultUninstallMode="2" launcherOverwriteMode="3" defaultFileMode="644" defaultDirMode="755">
+    <filesets>
+      <fileset name="Full file set" id="734" customizedId="" />
+      <fileset name="Mac OS X JRE" id="880" customizedId="" />
+      <fileset name="Windows JRE" id="882" customizedId="" />
+    </filesets>
+    <roots>
+      <root id="735" fileset="734" location="" />
+      <root id="881" fileset="880" location="" />
+      <root id="883" fileset="882" location="" />
+    </roots>
+    <mountPoints>
+      <mountPoint id="454" root="" location="" mode="755" />
+      <mountPoint id="736" root="735" location="" mode="755" />
+      <mountPoint id="884" root="881" location="" mode="755" />
+      <mountPoint id="885" root="883" location="" mode="755" />
+    </mountPoints>
+    <entries>
+      <dirEntry mountPoint="454" file="../../getdown/files/$$JAVA_VERSION$$" overwriteMode="4" shared="false" fileMode="644" uninstallMode="2" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="736" file="../../getdown/website/$$JAVA_VERSION$$" overwriteMode="4" shared="false" fileMode="644" uninstallMode="2" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="884" file="$$MACOS_JAVA_VM_DIR$$" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="jre" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="885" file="$$WINDOWS_JAVA_VM_DIR$$" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="jre" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+    </entries>
+    <components>
+      <component name="jalview_getdown" id="1031" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_734" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="macos_java_vm" id="1155" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_880" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="windows_java_vm" id="1156" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_882" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="getdown" id="1276" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+    </components>
+  </files>
+  <launchers>
+    <launcher name="Offline Jalview Launcher" id="737" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="false" macBundleIdentifier="" swtApp="false" fileset="734" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="${compiler:sys.shortName}" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="true" fileVersion="" fileDescription="${compiler:sys.shortName}" legalCopyright="..." internalName="${compiler:sys.shortName}" productName="${compiler:sys.shortName}" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments="." allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="true" />
+          <archive location="dist/commons-compress-1.18.jar" failOnError="true" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="1" file="">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/Jalview_Logo.png" />
+      </iconImageFiles>
+    </launcher>
+    <launcher name="Network Jalview Launcher" id="1402" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="false" macBundleIdentifier="" swtApp="false" fileset="" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="Jalview" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="true" fileVersion="" fileDescription="${compiler:sys.shortName}" legalCopyright="..." internalName="${compiler:sys.shortName}" productName="${compiler:sys.shortName}" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments="." allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="true" />
+          <archive location="dist/commons-compress-1.18.jar" failOnError="true" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="1" file="">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/Jalview_Logo.png" />
+      </iconImageFiles>
+    </launcher>
+  </launchers>
+  <installerGui installerType="1" addOnAppId="" suggestPreviousLocations="true" autoUpdateDescriptorUrl="" useAutoUpdateBaseUrl="false" autoUpdateBaseUrl="">
+    <staticMembers script="" />
+    <customCode />
+    <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+      <commentFiles />
+      <customAttributes />
+    </autoUpdate>
+    <applications>
+      <application name="" id="installer" customizedId="" beanClass="com.install4j.runtime.beans.applications.InstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="35" fileset="" customIcnsFile="../../resources/images/jalview_logos.icns" customIcoFile="../../resources/images/jalview_logos.ico" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.InstallerApplication">
+              <void property="useCustomIcon">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="146" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+          <styleOverride name="Jalview" enabled="true">
+            <formComponent name="Watermark" id="352" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                    <void property="enabledTitleText">
+                      <boolean>false</boolean>
+                    </void>
+                    <void property="labelText">
+                      <string>install4j</string>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <initScript />
+              <visibilityScript />
+              <externalParametrizationPropertyNames>
+                <propertyName>labelText</propertyName>
+              </externalParametrizationPropertyNames>
+            </formComponent>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="1" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="22" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="2" customizedId="" beanClass="com.install4j.runtime.beans.screens.WelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.WelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides>
+              <styleOverride name="Customize banner image" enabled="true">
+                <group name="" id="145" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+                  <serializedBean>
+                    <java class="java.beans.XMLDecoder">
+                      <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                        <void property="backgroundColor">
+                          <object class="java.awt.Color">
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="borderSides">
+                          <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                            <void property="bottom">
+                              <boolean>true</boolean>
+                            </void>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBackgroundColor">
+                          <object class="java.awt.Color">
+                            <int>25</int>
+                            <int>143</int>
+                            <int>220</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBorder">
+                          <boolean>true</boolean>
+                        </void>
+                        <void property="imageFile">
+                          <object class="com.install4j.api.beans.ExternalFile">
+                            <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                          </object>
+                        </void>
+                        <void property="insets">
+                          <object class="java.awt.Insets">
+                            <int>5</int>
+                            <int>10</int>
+                            <int>10</int>
+                            <int>10</int>
+                          </object>
+                        </void>
+                      </object>
+                    </java>
+                  </serializedBean>
+                  <beans />
+                  <externalParametrizationPropertyNames>
+                    <propertyName>imageAnchor</propertyName>
+                    <propertyName>imageEdgeBackgroundColor</propertyName>
+                    <propertyName>imageFile</propertyName>
+                  </externalParametrizationPropertyNames>
+                </group>
+              </styleOverride>
+            </styleOverrides>
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="7" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="3" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="4" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName());
+return console.askOkCancel(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="5" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Update Alert" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>updateCheck</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="" id="6" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="20" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:ClickNext}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="8" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition>!context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="11" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getVariable("sys.responseFile") == null</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="9" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="25" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectDirLabel(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="10" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Directory Chooser" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent">
+                      <void property="requestFocus">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>suggestAppDir</propertyName>
+                  <propertyName>validateApplicationId</propertyName>
+                  <propertyName>existingDirWarning</propertyName>
+                  <propertyName>checkWritable</propertyName>
+                  <propertyName>manualEntryAllowed</propertyName>
+                  <propertyName>checkFreeSpace</propertyName>
+                  <propertyName>showRequiredDiskSpace</propertyName>
+                  <propertyName>showFreeDiskSpace</propertyName>
+                  <propertyName>allowSpacesOnUnix</propertyName>
+                  <propertyName>validationScript</propertyName>
+                  <propertyName>standardValidation</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="12" customizedId="" beanClass="com.install4j.runtime.beans.screens.ComponentsScreen" enabled="false" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.ComponentsScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="13" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectComponentsLabel2}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="14" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Components" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent">
+                      <void property="fillVertical">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>selectionChangedScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="15" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="true" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="17" customizedId="" beanClass="com.install4j.runtime.beans.actions.InstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="2" errorMessage="${i18n:FileCorrupted}">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.InstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="18" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction">
+                      <void property="uninstallerMenuName">
+                        <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action name="" id="19" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction">
+                      <void property="itemName">
+                        <string>${compiler:sys.fullName} ${compiler:sys.version}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="124" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" enabled="false" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetVariableAction">
+                      <void property="script">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string />
+                          </void>
+                        </object>
+                      </void>
+                      <void property="variableName">
+                        <string />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>true</condition>
+              </action>
+              <action name="" id="134" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction">
+                      <void property="launcherId">
+                        <string>121</string>
+                      </void>
+                      <void property="vmOptions">
+                        <array class="java.lang.String" length="0" />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="16" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:WizardPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="20" customizedId="" beanClass="com.install4j.runtime.beans.screens.FinishedScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.FinishedScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="573" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction">
+                      <void property="name">
+                        <string>${compiler:sys.fullName}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("createDesktopLinkAction")</condition>
+              </action>
+              <action name="" id="575" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.AddStartupItemAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.AddStartupItemAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="576" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.AddToDockAction" enabled="false" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.AddToDockAction" />
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("addToDockAction")</condition>
+              </action>
+              <action name="" id="578" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Project File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvp</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>121</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/file.png</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/file.png</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="21" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:finishedMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add a desktop link" id="574" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:CreateDesktopIcon}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>createDesktopLinkAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add an executable to the dock" id="577" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:AddToDock}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>addToDockAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>Util.isMacOS()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+      <application name="" id="uninstaller" customizedId="" beanClass="com.install4j.runtime.beans.applications.UninstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="41" fileset="" customIcnsFile="" customIcoFile="" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.UninstallerApplication">
+              <void property="customMacosExecutableName">
+                <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+              </void>
+              <void property="useCustomMacosExecutableName">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="147" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>192</int>
+                        <int>192</int>
+                        <int>192</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="23" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="33" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="34" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="24" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="35" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="25" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="26" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConfirmUninstall", context.getApplicationName());
+return console.askYesNo(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="27" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="35" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="659" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="progressChangeType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.control.ProgressChangeType</class>
+                          <string>SET_INDETERMINATE</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="29" customizedId="" beanClass="com.install4j.runtime.beans.actions.UninstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.UninstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="1603" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="progressChangeType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.control.ProgressChangeType</class>
+                          <string>SET_DETERMINATE</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="1601" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="percentValue">
+                        <int>90</int>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="1525" customizedId="" beanClass="com.install4j.runtime.beans.actions.files.DeleteFileAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.files.DeleteFileAction">
+                      <void property="files">
+                        <array class="java.io.File" length="14">
+                          <void index="0">
+                            <object class="java.io.File">
+                              <string>jre</string>
+                            </object>
+                          </void>
+                          <void index="1">
+                            <object class="java.io.File">
+                              <string>.install4j</string>
+                            </object>
+                          </void>
+                          <void index="2">
+                            <object class="java.io.File">
+                              <string>dist</string>
+                            </object>
+                          </void>
+                          <void index="3">
+                            <object class="java.io.File">
+                              <string>resource</string>
+                            </object>
+                          </void>
+                          <void index="4">
+                            <object class="java.io.File">
+                              <string>getdown-launcher.jar</string>
+                            </object>
+                          </void>
+                          <void index="5">
+                            <object class="java.io.File">
+                              <string>getdown-launcher-old.jar</string>
+                            </object>
+                          </void>
+                          <void index="6">
+                            <object class="java.io.File">
+                              <string>getdown-launcher-new.jar</string>
+                            </object>
+                          </void>
+                          <void index="7">
+                            <object class="java.io.File">
+                              <string>digest.txt</string>
+                            </object>
+                          </void>
+                          <void index="8">
+                            <object class="java.io.File">
+                              <string>digest2.txt</string>
+                            </object>
+                          </void>
+                          <void index="9">
+                            <object class="java.io.File">
+                              <string>getdown.txt</string>
+                            </object>
+                          </void>
+                          <void index="10">
+                            <object class="java.io.File">
+                              <string>getdown-launcher.jarv</string>
+                            </object>
+                          </void>
+                          <void index="11">
+                            <object class="java.io.File">
+                              <string>gettingdown.lock</string>
+                            </object>
+                          </void>
+                          <void index="12">
+                            <object class="java.io.File">
+                              <string>launcher.log</string>
+                            </object>
+                          </void>
+                          <void index="13">
+                            <object class="java.io.File">
+                              <string>proxy.txt</string>
+                            </object>
+                          </void>
+                        </array>
+                      </void>
+                      <void property="recursive">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="660" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="percentValue">
+                        <int>100</int>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="28" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:UninstallerPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="32" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallFailureScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="35" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallFailureScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents />
+          </screen>
+          <screen name="" id="30" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallSuccessScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="35" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallSuccessScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="31" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:successMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+    </applications>
+    <styles defaultStyleId="35">
+      <style name="Standard" id="35" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <formComponent name="Header" id="36" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>48</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+          <group name="Main" id="37" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="imageEdgeAxisType">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.runtime.beans.formcomponents.AxisType</class>
+                      <string>HORIZONTAL</string>
+                    </object>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                    </object>
+                  </void>
+                  <void property="imageOverlap">
+                    <boolean>true</boolean>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="38" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="20" insetBottom="10" insetRight="20" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Watermark" id="39" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                      <void property="enabledTitleText">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="labelText">
+                        <string>install4j</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>labelText</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="Footer" id="40" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                      <void property="styleId">
+                        <string>52</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames />
+          </group>
+        </formComponents>
+      </style>
+      <style name="Banner" id="41" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <group name="" id="42" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="backgroundColor">
+                    <object class="java.awt.Color">
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="borderSides">
+                    <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                      <void property="bottom">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBackgroundColor">
+                    <object class="java.awt.Color">
+                      <int>25</int>
+                      <int>143</int>
+                      <int>220</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBorder">
+                    <boolean>true</boolean>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:sys.install4jHome}/resource/styles/wizard.png</string>
+                    </object>
+                  </void>
+                  <void property="insets">
+                    <object class="java.awt.Insets">
+                      <int>5</int>
+                      <int>10</int>
+                      <int>10</int>
+                      <int>10</int>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="43" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                      <void property="labelFontSizePercent">
+                        <int>130</int>
+                      </void>
+                      <void property="labelFontStyle">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                          <string>BOLD</string>
+                        </object>
+                      </void>
+                      <void property="labelFontType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                          <string>DERIVED</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="44" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="45" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames>
+              <propertyName>imageAnchor</propertyName>
+              <propertyName>imageEdgeBackgroundColor</propertyName>
+              <propertyName>imageFile</propertyName>
+            </externalParametrizationPropertyNames>
+          </group>
+          <formComponent name="" id="46" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>52</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+        </formComponents>
+      </style>
+      <group name="Style components" id="47" customizedId="" beanClass="com.install4j.runtime.beans.groups.StyleGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.groups.StyleGroup" />
+          </java>
+        </serializedBean>
+        <beans>
+          <style name="Standard header" id="48" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>NORTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="49" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize title bar" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                      <void property="backgroundColor">
+                        <object class="java.awt.Color">
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                        </object>
+                      </void>
+                      <void property="borderSides">
+                        <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                          <void property="bottom">
+                            <boolean>true</boolean>
+                          </void>
+                        </object>
+                      </void>
+                      <void property="imageAnchor">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.api.beans.Anchor</class>
+                          <string>NORTHEAST</string>
+                        </object>
+                      </void>
+                      <void property="imageEdgeBorderWidth">
+                        <int>2</int>
+                      </void>
+                      <void property="imageFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>icon:${installer:sys.installerApplicationMode}_header.png</string>
+                        </object>
+                      </void>
+                      <void property="imageInsets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>5</int>
+                          <int>1</int>
+                          <int>1</int>
+                        </object>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>20</int>
+                          <int>0</int>
+                          <int>10</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="Title" id="50" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="labelFontStyle">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                              <string>BOLD</string>
+                            </object>
+                          </void>
+                          <void property="labelFontType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                              <string>DERIVED</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Subtitle" id="51" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="8" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="titleType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.styles.TitleType</class>
+                              <string>SUB_TITLE</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames>
+                  <propertyName>backgroundColor</propertyName>
+                  <propertyName>foregroundColor</propertyName>
+                  <propertyName>imageAnchor</propertyName>
+                  <propertyName>imageFile</propertyName>
+                  <propertyName>imageOverlap</propertyName>
+                </externalParametrizationPropertyNames>
+              </group>
+            </formComponents>
+          </style>
+          <style name="Standard footer" id="52" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>SOUTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="53" customizedId="" beanClass="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup">
+                      <void property="alignFirstLabel">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>3</int>
+                          <int>5</int>
+                          <int>8</int>
+                          <int>5</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="" id="54" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SpringComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.formcomponents.SpringComponent" />
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Back button" id="55" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>&lt; ${i18n:ButtonBack}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>PREVIOUS</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Next button" id="56" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonNext} &gt;</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>NEXT</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Cancel button" id="57" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="5" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonCancel}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>CANCEL</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames />
+              </group>
+            </formComponents>
+          </style>
+        </beans>
+      </group>
+    </styles>
+  </installerGui>
+  <mediaSets>
+    <linuxDeb name="Linux Deb Archive" id="153" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" bzip="true" description="Jalview Desktop" maintainerEmail="help@jalview.org" architectureSet="false" architecture="">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxDeb>
+    <linuxRPM name="Linux RPM" id="570" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" os="linux" arch="i386">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxRPM>
+    <windows name="Offline Windows" id="743" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="$$WINDOWS_JAVA_VM_TGZ$$" manualJREEntry="true" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+    <macosArchive name="Offline macOS Single Bundle Archive" id="878" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}-app_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Installer" launcherId="737">
+      <excludedComponents>
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="./jalview_dmg_background.png" />
+        <file name=".DS_Store" file="./DS_Store" />
+        <symlink name="Jalview.app/Contents/Resources/app/jre/Contents/MacOS/libjli.dylib" target="../Home/lib/jli/libjli.dylib" />
+      </topLevelFiles>
+    </macosArchive>
+    <windows name="Network Windows" id="1272" customizedId="" mediaFileName="${compiler:sys.shortName}-NETWORK_${compiler:sys.platform}_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="$$WINDOWS_JAVA_VM_TGZ$$" manualJREEntry="true" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1155" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="737" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+    <macosArchive name="Network macOS Single Bundle Archive" id="1274" customizedId="" mediaFileName="${compiler:sys.shortName}-NETWORK_${compiler:sys.platform}-app_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Installer" launcherId="1402">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="./jalview_dmg_background.png" />
+        <file name=".DS_Store" file="./DS_Store" />
+        <symlink name="Jalview.app/Contents/Resources/app/jre/Contents/MacOS/libjli.dylib" target="../Home/lib/jli/libjli.dylib" />
+      </topLevelFiles>
+    </macosArchive>
+    <unixInstaller name="Unix Installer" id="1595" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_installer_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <installerScript mode="1" file="">
+        <content />
+      </installerScript>
+    </unixInstaller>
+    <unixArchive name="Unix Archive" id="1596" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_archive_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1155" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude />
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </unixArchive>
+    <windows name="Non-registry Windows offline" id="1600" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE-noreg_${compiler:sys.platform}_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="$$WINDOWS_JAVA_VM_TGZ$$" manualJREEntry="true" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans>
+        <bean refId="18" />
+        <bean refId="19" />
+        <bean refId="578" />
+      </excludedBeans>
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+  </mediaSets>
+  <buildIds buildAll="true">
+    <mediaSet refId="153" />
+    <mediaSet refId="570" />
+    <mediaSet refId="743" />
+    <mediaSet refId="878" />
+    <mediaSet refId="1272" />
+    <mediaSet refId="1274" />
+    <mediaSet refId="1595" />
+    <mediaSet refId="1596" />
+    <mediaSet refId="1598" />
+    <mediaSet refId="1600" />
+  </buildIds>
+  <buildOptions verbose="false" faster="false" disableSigning="false" disableJreBundling="false" debug="false" />
+</install4j>
diff --git a/utils/install4j/file_associations_auto-Info_plist.xml b/utils/install4j/file_associations_auto-Info_plist.xml
new file mode 100644 (file)
index 0000000..da2633b
--- /dev/null
@@ -0,0 +1,449 @@
+<key>CFBundleDocumentTypes</key>
+<array>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>jvp</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview+xml+zip</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>jvl</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview Version Locator File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-Version-Locator.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-jvl+text</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>amsa</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>AMSA File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-amsa</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>annotations</string>
+<string>jvannotations</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview Annotations File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-annotations+text</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>biojson</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>BioJSON File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-biojson+json</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>BLC</string>
+<string>blc</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>BLC File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-blc</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>aln</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Clustal File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-clustal</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>fa</string>
+<string>fasta</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Fasta File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-fasta</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>features</string>
+<string>jvfeatures</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Jalview Features File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-features+text</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>gff2</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Generic Features Format v2 File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-gff2</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>gff3</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Generic Features Format v3 File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-gff3</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>concise</string>
+<string>jnet</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>JnetFile File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-jnet+text</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>cif</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>mmCIF File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>chemical/x-cif</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>mcif</string>
+<string>mmcif</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>mmCIF File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>chemical/x-mcif</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>msf</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>MSF File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-msf</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>pdb</string>
+<string>ent</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>PDB File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>chemical/x-pdb</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>pfam</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>PFAM File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-pfam</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>phy</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>PHYLIP File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-phylip</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>pileup</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>PileUp File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-pileup</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>pir</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>PIR File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-pir</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>rnaml</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>RNAML File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/rnaml+xml</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>mat</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Substitution Matrix File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>application/x-jalview-scorematrix+text</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+<string>sto</string>
+<string>stk</string>
+</array>
+<key>CFBundleTypeName</key>
+<string>Stockholm File</string>
+<key>CFBundleTypeIconFile</key>
+<string>Jalview-File.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>text/x-stockholm</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
+</array>
diff --git a/utils/install4j/file_associations_auto-install4j.xml b/utils/install4j/file_associations_auto-install4j.xml
new file mode 100644 (file)
index 0000000..192b5d2
--- /dev/null
@@ -0,0 +1,1085 @@
+              <action name="" id="10000" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .jvp file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvp</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10001" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .jvl file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Version Locator File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvl</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-Version-Locator.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-Version-Locator.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10002" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .amsa file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>AMSA File</string>
+                      </void>
+                      <void property="extension">
+                        <string>amsa</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10003" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .annotations file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Annotations File</string>
+                      </void>
+                      <void property="extension">
+                        <string>annotations</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10004" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .jvannotations file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Annotations File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvannotations</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10005" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .biojson file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>BioJSON File</string>
+                      </void>
+                      <void property="extension">
+                        <string>biojson</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10006" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .BLC file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>BLC File</string>
+                      </void>
+                      <void property="extension">
+                        <string>BLC</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10007" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .blc file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>BLC File</string>
+                      </void>
+                      <void property="extension">
+                        <string>blc</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10008" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .aln file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Clustal File</string>
+                      </void>
+                      <void property="extension">
+                        <string>aln</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10009" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .fa file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Fasta File</string>
+                      </void>
+                      <void property="extension">
+                        <string>fa</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10010" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .fasta file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Fasta File</string>
+                      </void>
+                      <void property="extension">
+                        <string>fasta</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10011" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .features file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Features File</string>
+                      </void>
+                      <void property="extension">
+                        <string>features</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10012" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .jvfeatures file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Features File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvfeatures</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10013" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .gff2 file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Generic Features Format v2 File</string>
+                      </void>
+                      <void property="extension">
+                        <string>gff2</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10014" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .gff3 file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Generic Features Format v3 File</string>
+                      </void>
+                      <void property="extension">
+                        <string>gff3</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10015" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .concise file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>JnetFile File</string>
+                      </void>
+                      <void property="extension">
+                        <string>concise</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10016" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .jnet file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>JnetFile File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jnet</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10017" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .cif file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>mmCIF File</string>
+                      </void>
+                      <void property="extension">
+                        <string>cif</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10018" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .mcif file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>mmCIF File</string>
+                      </void>
+                      <void property="extension">
+                        <string>mcif</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10019" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .mmcif file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>mmCIF File</string>
+                      </void>
+                      <void property="extension">
+                        <string>mmcif</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10020" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .msf file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>MSF File</string>
+                      </void>
+                      <void property="extension">
+                        <string>msf</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10021" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .pdb file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PDB File</string>
+                      </void>
+                      <void property="extension">
+                        <string>pdb</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10022" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .ent file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PDB File</string>
+                      </void>
+                      <void property="extension">
+                        <string>ent</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10023" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .pfam file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PFAM File</string>
+                      </void>
+                      <void property="extension">
+                        <string>pfam</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10024" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .phy file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PHYLIP File</string>
+                      </void>
+                      <void property="extension">
+                        <string>phy</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10025" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .pileup file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PileUp File</string>
+                      </void>
+                      <void property="extension">
+                        <string>pileup</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10026" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .pir file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>PIR File</string>
+                      </void>
+                      <void property="extension">
+                        <string>pir</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10027" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .rnaml file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>RNAML File</string>
+                      </void>
+                      <void property="extension">
+                        <string>rnaml</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10028" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .mat file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Substitution Matrix File</string>
+                      </void>
+                      <void property="extension">
+                        <string>mat</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10029" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .sto file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Stockholm File</string>
+                      </void>
+                      <void property="extension">
+                        <string>sto</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
+              <action name="" id="10030" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .stk file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Stockholm File</string>
+                      </void>
+                      <void property="extension">
+                        <string>stk</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>Jalview-File.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
diff --git a/utils/install4j/file_associations_template-Info_plist.xml b/utils/install4j/file_associations_template-Info_plist.xml
new file mode 100644 (file)
index 0000000..7fa4fed
--- /dev/null
@@ -0,0 +1,19 @@
+<dict>
+<key>CFBundleTypeExtensions</key>
+<array>
+$$<string>EXTENSIONS</string>
+$$</array>
+<key>CFBundleTypeName</key>
+<string>$$NAME$$ File</string>
+<key>CFBundleTypeIconFile</key>
+<string>$$ICONFILE$$.icns</string>
+<key>CFBundleTypeRole</key>
+<string>Editor</string>
+<key>CFBundleTypeMIMETypes</key>
+<array>
+<string>$$MIMETYPE$$</string>
+</array>
+<key>LSIsAppleDefaultForType</key>
+<true/>
+</dict>
+
diff --git a/utils/install4j/file_associations_template-install4j.xml b/utils/install4j/file_associations_template-install4j.xml
new file mode 100644 (file)
index 0000000..697029a
--- /dev/null
@@ -0,0 +1,35 @@
+              <action name="" id="$$ID$$" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make .$$EXTENSION$$ file association">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>$$NAME$$ File</string>
+                      </void>
+                      <void property="extension">
+                        <string>$$EXTENSION$$</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>737</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>$$ICONFILE$$.icns</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>$$ICONFILE$$.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+
diff --git a/utils/install4j/install4j_template.install4j b/utils/install4j/install4j_template.install4j
new file mode 100644 (file)
index 0000000..7a924e2
--- /dev/null
@@ -0,0 +1,1886 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<install4j version="7.0.9" transformSequenceNumber="7">
+  <directoryPresets config="../../../../../buildtools/jre/openjdk-java_vm/macos-jdk8u202-b08-jre" />
+  <application name="Jalview" distributionSourceDir="" applicationId="6595-2347-1923-0725" mediaDir="../../build/install4j" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.platform}_${compiler:sys.version}" compression="6" lzmaCompression="true" pack200Compression="false" excludeSignedFromPacking="true" commonExternalFiles="false" createMd5Sums="true" shrinkRuntime="true" shortName="Jalview" publisher="University of Dundee" publisherWeb="http://www.jalview.org/" version="$$VERSION$$" allPathsRelative="true" backupOnSave="false" autoSave="false" convertDotsToUnderscores="true" macSignature="????" macVolumeId="5aac4968c304f65" javaMinVersion="9999999999" javaMaxVersion="" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
+    <languages skipLanguageSelection="false" languageSelectionInPrincipalLanguage="false">
+      <principalLanguage id="en" customLocalizationFile="" />
+      <additionalLanguages />
+    </languages>
+    <searchSequence>
+      <directory location="${compiler:JRE_DIR}" />
+    </searchSequence>
+    <variables>
+      <variable name="OSX_KEYSTORE" value="" description="" category="" />
+      <variable name="JSIGN_SH" value="" description="" category="" />
+      <variable name="JRE_DIR" value="jre" description="The folder under the app folder that the JRE will be either copied or unpacked into" category="" />
+    </variables>
+    <mergedProjects />
+    <codeSigning macEnabled="true" macPkcs12File="${compiler:OSX_KEYSTORE}" windowsEnabled="false" windowsKeySource="pkcs12" windowsPvkFile="" windowsSpcFile="" windowsPkcs12File="" windowsPkcs11Library="" windowsPkcs11Slot="">
+      <windowsKeystoreIdentifier issuer="" serial="" subject="" />
+      <windowsPkcs11Identifier issuer="" serial="" subject="" />
+    </codeSigning>
+  </application>
+  <files keepModificationTimes="false" missingFilesStrategy="warn" globalExcludeSuffixes="" defaultOverwriteMode="4" defaultUninstallMode="2" launcherOverwriteMode="3" defaultFileMode="644" defaultDirMode="755">
+    <filesets>
+      <fileset name="Full file set" id="734" customizedId="" />
+      <fileset name="Mac OS X JRE" id="880" customizedId="" />
+      <fileset name="Windows JRE" id="882" customizedId="" />
+    </filesets>
+    <roots>
+      <root id="735" fileset="734" location="" />
+      <root id="881" fileset="880" location="" />
+      <root id="883" fileset="882" location="" />
+    </roots>
+    <mountPoints>
+      <mountPoint id="454" root="" location="" mode="755" />
+      <mountPoint id="736" root="735" location="" mode="755" />
+      <mountPoint id="884" root="881" location="" mode="755" />
+      <mountPoint id="885" root="883" location="" mode="755" />
+    </mountPoints>
+    <entries>
+      <dirEntry mountPoint="454" file="../../getdown/files/$$JAVA_VERSION$$" overwriteMode="4" shared="false" fileMode="644" uninstallMode="2" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="736" file="../../getdown/website/$$JAVA_VERSION$$" overwriteMode="4" shared="false" fileMode="644" uninstallMode="2" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="884" file="$$MACOS_JAVA_VM_DIR$$" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="885" file="$$WINDOWS_JAVA_VM_DIR$$" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="${compiler:JRE_DIR}" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+    </entries>
+    <components>
+      <component name="jalview_getdown" id="1031" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_734" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="macos_java_vm" id="1155" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_880" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="windows_java_vm" id="1156" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_882" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+      <component name="getdown" id="1276" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+    </components>
+  </files>
+  <launchers>
+    <launcher name="Offline Jalview Launcher" id="737" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="true" macBundleIdentifier="$$MACOS_BUNDLE_ID$$" swtApp="false" fileset="734" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="${compiler:sys.shortName}" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="true" fileVersion="" fileDescription="${compiler:sys.shortName}" legalCopyright="$$COPYRIGHT_MESSAGE$$" internalName="${compiler:sys.shortName}" productName="${compiler:sys.shortName}" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments=". jalview" allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="true" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="2" file="$$INSTALL4JINFOPLISTFILEASSOCIATIONS$$">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/JalviewLogo_Huge.png" />
+      </iconImageFiles>
+    </launcher>
+    <launcher name="Network Jalview Launcher" id="1402" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="true" macBundleIdentifier="$$MACOS_BUNDLE_ID$$" swtApp="false" fileset="" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="Jalview" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="true" fileVersion="" fileDescription="${compiler:sys.shortName}" legalCopyright="$$COPYRIGHT_MESSAGE$$" internalName="${compiler:sys.shortName}" productName="${compiler:sys.shortName}" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments=". jalview" allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="true" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="2" file="$$INSTALL4JINFOPLISTFILEASSOCIATIONS$$">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/JalviewLogo_Huge.png" />
+      </iconImageFiles>
+    </launcher>
+  </launchers>
+  <installerGui installerType="1" addOnAppId="" suggestPreviousLocations="true" autoUpdateDescriptorUrl="https://www.jalview.org/install4j/updates.xml" useAutoUpdateBaseUrl="false" autoUpdateBaseUrl="">
+    <staticMembers script="" />
+    <customCode />
+    <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+      <commentFiles />
+      <customAttributes />
+    </autoUpdate>
+    <applications>
+      <application name="" id="installer" customizedId="" beanClass="com.install4j.runtime.beans.applications.InstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="35" fileset="" customIcnsFile="../../resources/images/jalview_logos.icns" customIcoFile="../../resources/images/jalview_logos.ico" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.InstallerApplication">
+              <void property="useCustomIcon">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="146" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+          <styleOverride name="Jalview" enabled="true">
+            <formComponent name="Watermark" id="352" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                    <void property="enabledTitleText">
+                      <boolean>false</boolean>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <initScript />
+              <visibilityScript />
+              <externalParametrizationPropertyNames>
+                <propertyName>labelText</propertyName>
+              </externalParametrizationPropertyNames>
+            </formComponent>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="1" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="22" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction">
+                      <void property="obtainIfAdminWin">
+                        <boolean>false</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="2" customizedId="" beanClass="com.install4j.runtime.beans.screens.WelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.WelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides>
+              <styleOverride name="Customize banner image" enabled="true">
+                <group name="" id="145" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+                  <serializedBean>
+                    <java class="java.beans.XMLDecoder">
+                      <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                        <void property="backgroundColor">
+                          <object class="java.awt.Color">
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="borderSides">
+                          <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                            <void property="bottom">
+                              <boolean>true</boolean>
+                            </void>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBackgroundColor">
+                          <object class="java.awt.Color">
+                            <int>25</int>
+                            <int>143</int>
+                            <int>220</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBorder">
+                          <boolean>true</boolean>
+                        </void>
+                        <void property="imageFile">
+                          <object class="com.install4j.api.beans.ExternalFile">
+                            <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                          </object>
+                        </void>
+                        <void property="insets">
+                          <object class="java.awt.Insets">
+                            <int>5</int>
+                            <int>10</int>
+                            <int>10</int>
+                            <int>10</int>
+                          </object>
+                        </void>
+                      </object>
+                    </java>
+                  </serializedBean>
+                  <beans />
+                  <externalParametrizationPropertyNames>
+                    <propertyName>imageAnchor</propertyName>
+                    <propertyName>imageEdgeBackgroundColor</propertyName>
+                    <propertyName>imageFile</propertyName>
+                  </externalParametrizationPropertyNames>
+                </group>
+              </styleOverride>
+            </styleOverrides>
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="7" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="3" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="4" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName());
+return console.askOkCancel(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="5" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Update Alert" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>updateCheck</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="" id="6" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="20" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:ClickNext}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="8" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition>!context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="11" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getVariable("sys.responseFile") == null</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="9" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="25" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectDirLabel(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="10" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Directory Chooser" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent">
+                      <void property="requestFocus">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>allowSpacesOnUnix</propertyName>
+                  <propertyName>checkFreeSpace</propertyName>
+                  <propertyName>checkWritable</propertyName>
+                  <propertyName>existingDirWarning</propertyName>
+                  <propertyName>manualEntryAllowed</propertyName>
+                  <propertyName>showFreeDiskSpace</propertyName>
+                  <propertyName>showRequiredDiskSpace</propertyName>
+                  <propertyName>standardValidation</propertyName>
+                  <propertyName>suggestAppDir</propertyName>
+                  <propertyName>validateApplicationId</propertyName>
+                  <propertyName>validationScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="12" customizedId="" beanClass="com.install4j.runtime.beans.screens.ComponentsScreen" enabled="false" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.ComponentsScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="13" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectComponentsLabel2}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="14" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Components" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent">
+                      <void property="fillVertical">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>selectionChangedScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="1692" customizedId="" beanClass="com.install4j.runtime.beans.screens.FileAssociationsScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.FileAssociationsScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="1693" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectAssociationsLabel}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="1694" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.FileAssociationsComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="File Associations" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.FileAssociationsComponent">
+                      <void property="fillVertical">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="showSelectionButtons">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>showSelectionButtons</propertyName>
+                  <propertyName>selectionButtonPosition</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="15" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="true" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="17" customizedId="" beanClass="com.install4j.runtime.beans.actions.InstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="2" errorMessage="${i18n:FileCorrupted}">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.InstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="18" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction">
+                      <void property="uninstallerMenuName">
+                        <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action name="" id="19" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction">
+                      <void property="itemName">
+                        <string>${compiler:sys.fullName} ${compiler:sys.version}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="124" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" enabled="false" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetVariableAction">
+                      <void property="script">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string />
+                          </void>
+                        </object>
+                      </void>
+                      <void property="variableName">
+                        <string />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>true</condition>
+              </action>
+              <action name="" id="134" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction">
+                      <void property="launcherId">
+                        <string>121</string>
+                      </void>
+                      <void property="vmOptions">
+                        <array class="java.lang.String" length="0" />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="16" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:WizardPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="20" customizedId="" beanClass="com.install4j.runtime.beans.screens.FinishedScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.FinishedScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="573" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="Could not make desktop link">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction">
+                      <void property="allUsers">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="description">
+                        <string>${compiler:sys.shortName}</string>
+                      </void>
+                      <void property="file">
+                        <object class="java.io.File">
+                          <string>${compiler:sys.shortName}</string>
+                        </object>
+                      </void>
+                      <void property="name">
+                        <string>${compiler:sys.fullName}</string>
+                      </void>
+                      <void property="unixIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/JalviewLogo_Huge.png</string>
+                        </object>
+                      </void>
+                      <void property="winIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/jalview_logos.ico</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("createDesktopLinkAction")</condition>
+              </action>
+              <action name="" id="576" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.AddToDockAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.AddToDockAction">
+                      <void property="executable">
+                        <object class="java.io.File">
+                          <string>Jalview.app</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("addToDockAction")</condition>
+              </action>
+              <action name="EXTENSIONS_REPLACED_BY_GRADLE" id="1691" customizedId="EXTENSIONS" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>This action, identified by its name "EXTENSIONS_REPLACED_BY_GRADLE", will be replaced by gradle with the contents of file 'file_associations_auto_install4j.xml'.</string>
+                      </void>
+                      <void property="extension">
+                        <string>extensions_to_be_replaced_by_gradle</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="21" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:finishedMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add a desktop link" id="574" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:CreateDesktopIcon}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>createDesktopLinkAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add an executable to the dock" id="577" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:AddToDock}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>addToDockAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>Util.isMacOS()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+      <application name="" id="uninstaller" customizedId="" beanClass="com.install4j.runtime.beans.applications.UninstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="35" fileset="" customIcnsFile="" customIcoFile="" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.UninstallerApplication">
+              <void property="customMacosExecutableName">
+                <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+              </void>
+              <void property="useCustomMacosExecutableName">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="147" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>192</int>
+                        <int>192</int>
+                        <int>192</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="23" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="33" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="34" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="24" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="25" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="26" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConfirmUninstall", context.getApplicationName());
+return console.askYesNo(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="27" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="659" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="progressChangeType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.control.ProgressChangeType</class>
+                          <string>SET_INDETERMINATE</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="29" customizedId="" beanClass="com.install4j.runtime.beans.actions.UninstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.UninstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="660" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="false" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="percentValue">
+                        <int>95</int>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="1525" customizedId="" beanClass="com.install4j.runtime.beans.actions.files.DeleteFileAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.files.DeleteFileAction">
+                      <void property="files">
+                        <array class="java.io.File" length="17">
+                          <void index="0">
+                            <object class="java.io.File">
+                              <string>jre</string>
+                            </object>
+                          </void>
+                          <void index="1">
+                            <object class="java.io.File">
+                              <string>.install4j</string>
+                            </object>
+                          </void>
+                          <void index="2">
+                            <object class="java.io.File">
+                              <string>dist</string>
+                            </object>
+                          </void>
+                          <void index="3">
+                            <object class="java.io.File">
+                              <string>resource</string>
+                            </object>
+                          </void>
+                          <void index="4">
+                            <object class="java.io.File">
+                              <string>getdown-launcher.jar</string>
+                            </object>
+                          </void>
+                          <void index="5">
+                            <object class="java.io.File">
+                              <string>getdown-launcher-old.jar</string>
+                            </object>
+                          </void>
+                          <void index="6">
+                            <object class="java.io.File">
+                              <string>getdown-launcher-new.jar</string>
+                            </object>
+                          </void>
+                          <void index="7">
+                            <object class="java.io.File">
+                              <string>*.jarv</string>
+                            </object>
+                          </void>
+                          <void index="8">
+                            <object class="java.io.File">
+                              <string>gettingdown.lock</string>
+                            </object>
+                          </void>
+                          <void index="9">
+                            <object class="java.io.File">
+                              <string>*.log</string>
+                            </object>
+                          </void>
+                          <void index="10">
+                            <object class="java.io.File">
+                              <string>*.txt</string>
+                            </object>
+                          </void>
+                          <void index="11">
+                            <object class="java.io.File">
+                              <string>*_new</string>
+                            </object>
+                          </void>
+                          <void index="12">
+                            <object class="java.io.File">
+                              <string>digest.txt</string>
+                            </object>
+                          </void>
+                          <void index="13">
+                            <object class="java.io.File">
+                              <string>digest2.txt</string>
+                            </object>
+                          </void>
+                          <void index="14">
+                            <object class="java.io.File">
+                              <string>getdown-launcher.jarv</string>
+                            </object>
+                          </void>
+                          <void index="15">
+                            <object class="java.io.File">
+                              <string>launcher.log</string>
+                            </object>
+                          </void>
+                          <void index="16">
+                            <object class="java.io.File">
+                              <string>proxy.txt</string>
+                            </object>
+                          </void>
+                        </array>
+                      </void>
+                      <void property="recursive">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="1791" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="percentValue">
+                        <int>100</int>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="28" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:UninstallerPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="32" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallFailureScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallFailureScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents />
+          </screen>
+          <screen name="" id="30" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallSuccessScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="41" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallSuccessScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="31" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:successMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+    </applications>
+    <styles defaultStyleId="35">
+      <style name="Standard" id="35" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <formComponent name="Header" id="36" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>48</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+          <group name="Main" id="37" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="imageEdgeAxisType">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.runtime.beans.formcomponents.AxisType</class>
+                      <string>HORIZONTAL</string>
+                    </object>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                    </object>
+                  </void>
+                  <void property="imageOverlap">
+                    <boolean>true</boolean>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="38" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="20" insetBottom="10" insetRight="20" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Watermark" id="39" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                      <void property="enabledTitleText">
+                        <boolean>false</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>labelText</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="Footer" id="40" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                      <void property="styleId">
+                        <string>52</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames />
+          </group>
+        </formComponents>
+      </style>
+      <style name="Banner" id="41" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <group name="" id="42" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="backgroundColor">
+                    <object class="java.awt.Color">
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="borderSides">
+                    <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                      <void property="bottom">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBackgroundColor">
+                    <object class="java.awt.Color">
+                      <int>25</int>
+                      <int>143</int>
+                      <int>220</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBorder">
+                    <boolean>true</boolean>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:sys.install4jHome}/resource/styles/wizard.png</string>
+                    </object>
+                  </void>
+                  <void property="insets">
+                    <object class="java.awt.Insets">
+                      <int>5</int>
+                      <int>10</int>
+                      <int>10</int>
+                      <int>10</int>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="43" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                      <void property="labelFontSizePercent">
+                        <int>130</int>
+                      </void>
+                      <void property="labelFontStyle">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                          <string>BOLD</string>
+                        </object>
+                      </void>
+                      <void property="labelFontType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                          <string>DERIVED</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="44" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="45" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames>
+              <propertyName>imageAnchor</propertyName>
+              <propertyName>imageEdgeBackgroundColor</propertyName>
+              <propertyName>imageFile</propertyName>
+            </externalParametrizationPropertyNames>
+          </group>
+          <formComponent name="" id="46" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>52</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+        </formComponents>
+      </style>
+      <group name="Style components" id="47" customizedId="" beanClass="com.install4j.runtime.beans.groups.StyleGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.groups.StyleGroup" />
+          </java>
+        </serializedBean>
+        <beans>
+          <style name="Standard header" id="48" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>NORTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="49" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize title bar" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                      <void property="backgroundColor">
+                        <object class="java.awt.Color">
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                        </object>
+                      </void>
+                      <void property="borderSides">
+                        <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                          <void property="bottom">
+                            <boolean>true</boolean>
+                          </void>
+                        </object>
+                      </void>
+                      <void property="imageAnchor">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.api.beans.Anchor</class>
+                          <string>NORTHEAST</string>
+                        </object>
+                      </void>
+                      <void property="imageEdgeBorderWidth">
+                        <int>2</int>
+                      </void>
+                      <void property="imageFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>icon:${installer:sys.installerApplicationMode}_header.png</string>
+                        </object>
+                      </void>
+                      <void property="imageInsets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>5</int>
+                          <int>1</int>
+                          <int>1</int>
+                        </object>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>20</int>
+                          <int>0</int>
+                          <int>10</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="Title" id="50" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="labelFontStyle">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                              <string>BOLD</string>
+                            </object>
+                          </void>
+                          <void property="labelFontType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                              <string>DERIVED</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Subtitle" id="51" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="8" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="titleType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.styles.TitleType</class>
+                              <string>SUB_TITLE</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames>
+                  <propertyName>backgroundColor</propertyName>
+                  <propertyName>foregroundColor</propertyName>
+                  <propertyName>imageAnchor</propertyName>
+                  <propertyName>imageFile</propertyName>
+                  <propertyName>imageOverlap</propertyName>
+                </externalParametrizationPropertyNames>
+              </group>
+            </formComponents>
+          </style>
+          <style name="Standard footer" id="52" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>SOUTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="53" customizedId="" beanClass="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup">
+                      <void property="alignFirstLabel">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>3</int>
+                          <int>5</int>
+                          <int>8</int>
+                          <int>5</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="" id="54" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SpringComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.formcomponents.SpringComponent" />
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Back button" id="55" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>&lt; ${i18n:ButtonBack}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>PREVIOUS</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Next button" id="56" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonNext} &gt;</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>NEXT</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Cancel button" id="57" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="5" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonCancel}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>CANCEL</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames />
+              </group>
+            </formComponents>
+          </style>
+        </beans>
+      </group>
+    </styles>
+  </installerGui>
+  <mediaSets>
+    <linuxDeb name="Linux Deb Archive" id="153" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" bzip="true" description="Jalview Desktop" maintainerEmail="help@jalview.org" architectureSet="false" architecture="">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxDeb>
+    <linuxRPM name="Linux RPM" id="570" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" os="linux" arch="i386">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxRPM>
+    <windows name="Offline Windows" id="743" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="$$WINDOWS_JAVA_VM_TGZ$$" manualJREEntry="true" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="~/AppData/Local" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents>
+        <component id="1155" />
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+    <macosArchive name="Offline macOS Single Bundle Archive" id="878" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}-app_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Installer" launcherId="737">
+      <excludedComponents>
+        <component id="1156" />
+        <component id="1276" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="./jalview_dmg_background.png" />
+        <file name=".DS_Store" file="./DS_Store" />
+        <symlink name="Jalview.app/Contents/Resources/app/${compiler:JRE_DIR}/Contents/MacOS/libjli.dylib" target="../Home/lib/jli/libjli.dylib" />
+        <file name="Jalview.app/Contents/Resources/Jalview-File.icns" file="./Jalview-File.icns" />
+        <file name="Jalview.app/Contents/Resources/Jalview-Version-Locator.icns" file="Jalview-Version-Locator.icns" />
+      </topLevelFiles>
+    </macosArchive>
+    <macosArchive name="Network macOS Single Bundle Archive" id="1274" customizedId="" mediaFileName="${compiler:sys.shortName}-NETWORK_${compiler:sys.platform}-app_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Installer" launcherId="1402">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="./jalview_dmg_background.png" />
+        <file name=".DS_Store" file="./DS_Store" />
+        <symlink name="Jalview.app/Contents/Resources/app/${compiler:JRE_DIR}/Contents/MacOS/libjli.dylib" target="../Home/lib/jli/libjli.dylib" />
+        <file name="Jalview.app/Contents/Resources/Jalview-Version-Locator.icns" file="Jalview-Version-Locator.icns" />
+        <file name="Jalview.app/Contents/Resources/Jalview-File.icns" file="Jalview-File.icns" />
+      </topLevelFiles>
+    </macosArchive>
+    <unixInstaller name="Unix Installer" id="1595" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_installer_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <installerScript mode="1" file="">
+        <content />
+      </installerScript>
+    </unixInstaller>
+    <unixArchive name="Unix Archive" id="1596" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_archive_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1155" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="1402" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude />
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </unixArchive>
+    <windows name="Network Windows" id="1862" customizedId="" mediaFileName="${compiler:sys.shortName}-NETWORK_${compiler:sys.platform}_${compiler:sys.version}-j$$JAVA_INTEGER_VERSION$$" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="true" postProcessor="${compiler:JSIGN_SH} $EXECUTABLE" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="$$WINDOWS_JAVA_VM_TGZ$$" manualJREEntry="true" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="~/AppData/Local" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents>
+        <component id="1031" />
+        <component id="1155" />
+        <component id="1156" />
+      </excludedComponents>
+      <includedDownloadableComponents />
+      <excludedLaunchers />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+  </mediaSets>
+  <buildIds buildAll="true">
+    <mediaSet refId="153" />
+    <mediaSet refId="570" />
+    <mediaSet refId="743" />
+    <mediaSet refId="878" />
+    <mediaSet refId="1274" />
+    <mediaSet refId="1595" />
+    <mediaSet refId="1596" />
+    <mediaSet refId="1862" />
+  </buildIds>
+  <buildOptions verbose="false" faster="false" disableSigning="false" disableJreBundling="false" debug="false" />
+</install4j>
diff --git a/utils/install4j/jalview_dmg_background.png b/utils/install4j/jalview_dmg_background.png
new file mode 100644 (file)
index 0000000..cfd924d
Binary files /dev/null and b/utils/install4j/jalview_dmg_background.png differ
diff --git a/utils/install4j/jalview_getdown.install4j b/utils/install4j/jalview_getdown.install4j
new file mode 100644 (file)
index 0000000..66bd257
--- /dev/null
@@ -0,0 +1,1774 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<install4j version="7.0.9" transformSequenceNumber="7">
+  <directoryPresets config="libjli.dylib" />
+  <application name="Jalview" distributionSourceDir="" applicationId="6595-2347-1923-0725" mediaDir="../../build/install4j" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.platform}_${compiler:sys.version}" compression="6" lzmaCompression="true" pack200Compression="false" excludeSignedFromPacking="true" commonExternalFiles="false" createMd5Sums="true" shrinkRuntime="true" shortName="Jalview" publisher="University of Dundee" publisherWeb="http://www.jalview.org/" version="DEVELOPMENT" allPathsRelative="true" backupOnSave="false" autoSave="false" convertDotsToUnderscores="true" macSignature="????" macVolumeId="5aac4968c304f65" javaMinVersion="1.8" javaMaxVersion="" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
+    <languages skipLanguageSelection="false" languageSelectionInPrincipalLanguage="false">
+      <principalLanguage id="en" customLocalizationFile="" />
+      <additionalLanguages />
+    </languages>
+    <searchSequence>
+      <directory location="java_vm" />
+    </searchSequence>
+    <variables />
+    <mergedProjects />
+    <codeSigning macEnabled="false" macPkcs12File="" windowsEnabled="false" windowsKeySource="pkcs12" windowsPvkFile="" windowsSpcFile="" windowsPkcs12File="" windowsPkcs11Library="" windowsPkcs11Slot="">
+      <windowsKeystoreIdentifier issuer="" serial="" subject="" />
+      <windowsPkcs11Identifier issuer="" serial="" subject="" />
+    </codeSigning>
+  </application>
+  <files keepModificationTimes="false" missingFilesStrategy="warn" globalExcludeSuffixes="" defaultOverwriteMode="4" defaultUninstallMode="2" launcherOverwriteMode="3" defaultFileMode="644" defaultDirMode="755">
+    <filesets>
+      <fileset name="Full file set" id="734" customizedId="" />
+      <fileset name="Mac OS X JRE" id="880" customizedId="" />
+      <fileset name="Windows JRE" id="882" customizedId="" />
+      <fileset name="Full Files With MacOS JRE" id="1015" customizedId="" />
+      <fileset name="Full Files With Windows JRE" id="1017" customizedId="" />
+    </filesets>
+    <roots>
+      <root id="735" fileset="734" location="" />
+      <root id="881" fileset="880" location="" />
+      <root id="883" fileset="882" location="" />
+      <root id="1016" fileset="1015" location="" />
+      <root id="1018" fileset="1017" location="" />
+    </roots>
+    <mountPoints>
+      <mountPoint id="454" root="" location="" mode="755" />
+      <mountPoint id="736" root="735" location="" mode="755" />
+      <mountPoint id="884" root="881" location="" mode="755" />
+      <mountPoint id="885" root="883" location="" mode="755" />
+      <mountPoint id="1019" root="1016" location="" mode="755" />
+      <mountPoint id="1020" root="1018" location="" mode="755" />
+    </mountPoints>
+    <entries>
+      <dirEntry mountPoint="454" file="../../getdown/files" overwriteMode="4" shared="false" fileMode="644" uninstallMode="0" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="736" file="../../getdown/website" overwriteMode="4" shared="false" fileMode="644" uninstallMode="0" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="884" file="./jres/macosx-amd64-1.8.0_202/java_vm" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="java_vm" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="885" file="./jres/windows-amd64-1.8.0_202/java_vm" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="java_vm" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="1019" file="../../getdown/website" overwriteMode="4" shared="false" fileMode="644" uninstallMode="0" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="1019" file="./jres/macosx-amd64-1.8.0_202/java_vm" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="java_vm" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="1020" file="../../getdown/website" overwriteMode="4" shared="false" fileMode="644" uninstallMode="0" overrideFileMode="false" overrideOverwriteMode="true" overrideUninstallMode="true" entryMode="direct" subDirectory="files" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+      <dirEntry mountPoint="1020" file="./jres/windows-amd64-1.8.0_202/java_vm" overwriteMode="4" shared="false" fileMode="755" uninstallMode="0" overrideFileMode="true" overrideOverwriteMode="false" overrideUninstallMode="true" entryMode="subdir" subDirectory="java_vm" excludeSuffixes="" dirMode="755" overrideDirMode="false">
+        <exclude />
+      </dirEntry>
+    </entries>
+    <components>
+      <component name="jalview_getdown" id="1031" customizedId="" displayDescription="false" hideHelpButton="false" selected="true" changeable="true" downloadable="false" hidden="false">
+        <description />
+        <include all="false">
+          <entry location=".i4j_fileset_734" fileType="regular" />
+          <entry location=".i4j_fileset_880" fileType="regular" />
+          <entry location=".i4j_fileset_882" fileType="regular" />
+        </include>
+        <dependencies />
+      </component>
+    </components>
+  </files>
+  <launchers>
+    <launcher name="Jalview Launcher" id="121" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}-${compiler:sys.version}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="false" macBundleIdentifier="" swtApp="false" fileset="" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="Jalview" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="false" fileVersion="" fileDescription="" legalCopyright="" internalName="" productName="" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments="." allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="false" />
+          <archive location="commons-compress-1.18.jar" failOnError="false" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="1" file="">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/Jalview_Logo.png" />
+      </iconImageFiles>
+    </launcher>
+    <launcher name="MacOS Offline Jalview Launcher" id="737" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}-${compiler:sys.version}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="false" macBundleIdentifier="" swtApp="false" fileset="1015" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="Jalview" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="false" fileVersion="" fileDescription="" legalCopyright="" internalName="" productName="" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments="." allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="false" />
+          <archive location="commons-compress-1.18.jar" failOnError="false" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="1" file="">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/Jalview_Logo.png" />
+      </iconImageFiles>
+    </launcher>
+    <launcher name="Windows Offline Jalview Launcher" id="1022" customizedId="" external="false" excludeFromMenu="false" unixMode="755" unixAutoStart="true" menuName="${compiler:sys.shortName}-${compiler:sys.version}" icnsFile="../../resources/images/jalview_logos.icns" customMacBundleIdentifier="false" macBundleIdentifier="" swtApp="false" fileset="1017" macBundleBinary="JavaApplicationStub" addMacEntitlements="false" macEntitlementsFile="" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:sys.shortName}" useJavaMinVersionOverride="false" javaMinVersionOverride="" useJavaMaxVersionOverride="false" javaMaxVersionOverride="" checkUpdater="false" updateExecutionMode="unattendedProgress" unattendedUpdateTitle="${i18n:updater.WindowTitle(&quot;${compiler:sys.fullName}&quot;)}">
+      <executable name="Jalview" type="1" iconSet="true" iconFile="../../resources/images/jalview_logos.ico" executableDir="." redirectStderr="true" stderrFile="error.log" stderrMode="overwrite" redirectStdout="true" stdoutFile="output.log" stdoutMode="overwrite" failOnStderrOutput="true" executableMode="1" changeWorkingDirectory="true" workingDirectory="." singleInstance="true" serviceStartType="2" serviceDependencies="" serviceDescription="" jreLocation="" executionLevel="asInvoker" checkConsoleParameter="true" globalSingleInstance="false" singleInstanceActivate="true" dpiAware="java9+">
+        <versionInfo include="false" fileVersion="" fileDescription="" legalCopyright="" internalName="" productName="" />
+      </executable>
+      <splashScreen show="false" width="640" height="480" bitmapFile="../../resources/images/jalview_logo_background_fade-640x480.png" textOverlay="true">
+        <text>
+          <statusLine x="85" y="81" text="${compiler:sys.shortName}" fontSize="18" fontColor="0,0,0" bold="false" />
+          <versionLine x="85" y="109" text="version ${compiler:sys.version}" fontSize="8" fontColor="0,0,0" bold="false" />
+        </text>
+      </splashScreen>
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" mainMode="1" vmParameters="" arguments="." allowVMPassthroughParameters="true" preferredVM="" bundleRuntime="true">
+        <classPath>
+          <archive location="getdown-launcher.jar" failOnError="false" />
+          <archive location="commons-compress-1.18.jar" failOnError="false" />
+        </classPath>
+        <modulePath />
+        <nativeLibraryDirectories />
+        <vmOptions />
+      </java>
+      <includedFiles />
+      <unextractableFiles />
+      <vmOptionsFile mode="template" overwriteMode="0" fileMode="644">
+        <content />
+      </vmOptionsFile>
+      <customScript mode="1" file="">
+        <content />
+      </customScript>
+      <infoPlist mode="1" file="">
+        <content />
+      </infoPlist>
+      <iconImageFiles>
+        <file path="../../resources/images/Jalview_Logo.png" />
+      </iconImageFiles>
+    </launcher>
+  </launchers>
+  <installerGui installerType="1" addOnAppId="" suggestPreviousLocations="true" autoUpdateDescriptorUrl="" useAutoUpdateBaseUrl="false" autoUpdateBaseUrl="">
+    <staticMembers script="" />
+    <customCode />
+    <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+      <commentFiles />
+      <customAttributes />
+    </autoUpdate>
+    <applications>
+      <application name="" id="installer" customizedId="" beanClass="com.install4j.runtime.beans.applications.InstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="35" fileset="" customIcnsFile="../../resources/images/jalview_logos.icns" customIcoFile="../../resources/images/jalview_logos.ico" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.InstallerApplication">
+              <void property="useCustomIcon">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="146" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+          <styleOverride name="Jalview" enabled="true">
+            <formComponent name="Watermark" id="352" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                    <void property="enabledTitleText">
+                      <boolean>false</boolean>
+                    </void>
+                    <void property="labelText">
+                      <string>install4j</string>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <initScript />
+              <visibilityScript />
+              <externalParametrizationPropertyNames>
+                <propertyName>labelText</propertyName>
+              </externalParametrizationPropertyNames>
+            </formComponent>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="1" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="22" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="2" customizedId="" beanClass="com.install4j.runtime.beans.screens.WelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.WelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides>
+              <styleOverride name="Customize banner image" enabled="true">
+                <group name="" id="145" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+                  <serializedBean>
+                    <java class="java.beans.XMLDecoder">
+                      <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                        <void property="backgroundColor">
+                          <object class="java.awt.Color">
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="borderSides">
+                          <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                            <void property="bottom">
+                              <boolean>true</boolean>
+                            </void>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBackgroundColor">
+                          <object class="java.awt.Color">
+                            <int>25</int>
+                            <int>143</int>
+                            <int>220</int>
+                            <int>255</int>
+                          </object>
+                        </void>
+                        <void property="imageEdgeBorder">
+                          <boolean>true</boolean>
+                        </void>
+                        <void property="imageFile">
+                          <object class="com.install4j.api.beans.ExternalFile">
+                            <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                          </object>
+                        </void>
+                        <void property="insets">
+                          <object class="java.awt.Insets">
+                            <int>5</int>
+                            <int>10</int>
+                            <int>10</int>
+                            <int>10</int>
+                          </object>
+                        </void>
+                      </object>
+                    </java>
+                  </serializedBean>
+                  <beans />
+                  <externalParametrizationPropertyNames>
+                    <propertyName>imageAnchor</propertyName>
+                    <propertyName>imageEdgeBackgroundColor</propertyName>
+                    <propertyName>imageFile</propertyName>
+                  </externalParametrizationPropertyNames>
+                </group>
+              </styleOverride>
+            </styleOverrides>
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="7" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="3" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="4" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName());
+return console.askOkCancel(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="5" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Update Alert" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>updateCheck</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="" id="6" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="20" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:ClickNext}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="8" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition>!context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="11" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="true" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction">
+                      <void property="excludedVariables">
+                        <array class="java.lang.String" length="1">
+                          <void index="0">
+                            <string>sys.installationDir</string>
+                          </void>
+                        </array>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getVariable("sys.responseFile") == null</condition>
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="9" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="25" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectDirLabel(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="10" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Directory Chooser" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent">
+                      <void property="requestFocus">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>suggestAppDir</propertyName>
+                  <propertyName>validateApplicationId</propertyName>
+                  <propertyName>existingDirWarning</propertyName>
+                  <propertyName>checkWritable</propertyName>
+                  <propertyName>manualEntryAllowed</propertyName>
+                  <propertyName>checkFreeSpace</propertyName>
+                  <propertyName>showRequiredDiskSpace</propertyName>
+                  <propertyName>showFreeDiskSpace</propertyName>
+                  <propertyName>allowSpacesOnUnix</propertyName>
+                  <propertyName>validationScript</propertyName>
+                  <propertyName>standardValidation</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="12" customizedId="" beanClass="com.install4j.runtime.beans.screens.ComponentsScreen" enabled="false" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.ComponentsScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="13" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${i18n:SelectComponentsLabel2}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="14" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Installation Components" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent">
+                      <void property="fillVertical">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>selectionChangedScript</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="15" customizedId="" beanClass="com.install4j.runtime.beans.screens.InstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="true" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.InstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="17" customizedId="" beanClass="com.install4j.runtime.beans.actions.InstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="2" errorMessage="${i18n:FileCorrupted}">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.InstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="18" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction">
+                      <void property="uninstallerMenuName">
+                        <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
+              </action>
+              <action name="" id="19" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction">
+                      <void property="itemName">
+                        <string>${compiler:sys.fullName} ${compiler:sys.version}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="124" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetVariableAction" enabled="false" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetVariableAction">
+                      <void property="script">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string />
+                          </void>
+                        </object>
+                      </void>
+                      <void property="variableName">
+                        <string />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>true</condition>
+              </action>
+              <action name="" id="134" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.AddVmOptionsAction">
+                      <void property="launcherId">
+                        <string>121</string>
+                      </void>
+                      <void property="vmOptions">
+                        <array class="java.lang.String" length="0" />
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="16" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:WizardPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="20" customizedId="" beanClass="com.install4j.runtime.beans.screens.FinishedScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.FinishedScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="573" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateDesktopLinkAction">
+                      <void property="name">
+                        <string>${compiler:sys.fullName}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("createDesktopLinkAction")</condition>
+              </action>
+              <action name="" id="575" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.AddStartupItemAction" enabled="false" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.AddStartupItemAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="576" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.AddToDockAction" enabled="false" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.AddToDockAction" />
+                  </java>
+                </serializedBean>
+                <condition>context.getBooleanVariable("addToDockAction")</condition>
+              </action>
+              <action name="" id="578" customizedId="" beanClass="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.desktop.CreateFileAssociationAction">
+                      <void property="description">
+                        <string>Jalview Project File</string>
+                      </void>
+                      <void property="extension">
+                        <string>jvp</string>
+                      </void>
+                      <void property="launcherId">
+                        <string>121</string>
+                      </void>
+                      <void property="macIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/file.png</string>
+                        </object>
+                      </void>
+                      <void property="macRole">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.desktop.MacAssociationRole</class>
+                          <string>EDITOR</string>
+                        </object>
+                      </void>
+                      <void property="windowsIconFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>../../resources/images/file.png</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="21" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:finishedMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add a desktop link" id="574" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:CreateDesktopIcon}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>createDesktopLinkAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Add an executable to the dock" id="577" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.CheckboxComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.CheckboxComponent">
+                      <void property="checkboxText">
+                        <string>${i18n:AddToDock}</string>
+                      </void>
+                      <void property="initiallySelected">
+                        <boolean>true</boolean>
+                      </void>
+                      <void property="variableName">
+                        <string>addToDockAction</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>Util.isMacOS()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+      <application name="" id="uninstaller" customizedId="" beanClass="com.install4j.runtime.beans.applications.UninstallerApplication" enabled="true" commentSet="false" comment="" actionElevationType="none" styleId="41" fileset="" customIcnsFile="" customIcoFile="" macEntitlementsFile="" automaticLauncherIntegration="false" launchMode="startupFirstWindow" launchInNewProcess="true" launchSchedule="updateSchedule" allLaunchers="true">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.applications.UninstallerApplication">
+              <void property="customMacosExecutableName">
+                <string>${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</string>
+              </void>
+              <void property="useCustomMacosExecutableName">
+                <boolean>true</boolean>
+              </void>
+            </object>
+          </java>
+        </serializedBean>
+        <styleOverrides>
+          <styleOverride name="Customize banner image" enabled="true">
+            <group name="" id="147" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+              <serializedBean>
+                <java class="java.beans.XMLDecoder">
+                  <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                    <void property="backgroundColor">
+                      <object class="java.awt.Color">
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="borderSides">
+                      <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                        <void property="bottom">
+                          <boolean>true</boolean>
+                        </void>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBackgroundColor">
+                      <object class="java.awt.Color">
+                        <int>192</int>
+                        <int>192</int>
+                        <int>192</int>
+                        <int>255</int>
+                      </object>
+                    </void>
+                    <void property="imageEdgeBorder">
+                      <boolean>true</boolean>
+                    </void>
+                    <void property="imageFile">
+                      <object class="com.install4j.api.beans.ExternalFile">
+                        <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                      </object>
+                    </void>
+                    <void property="insets">
+                      <object class="java.awt.Insets">
+                        <int>5</int>
+                        <int>10</int>
+                        <int>10</int>
+                        <int>10</int>
+                      </object>
+                    </void>
+                  </object>
+                </java>
+              </serializedBean>
+              <beans />
+              <externalParametrizationPropertyNames>
+                <propertyName>imageAnchor</propertyName>
+                <propertyName>imageEdgeBackgroundColor</propertyName>
+                <propertyName>imageFile</propertyName>
+              </externalParametrizationPropertyNames>
+            </group>
+          </styleOverride>
+        </styleOverrides>
+        <customScript mode="1" file="">
+          <content />
+        </customScript>
+        <launcherIds />
+        <variables />
+        <startup>
+          <screen name="" id="23" customizedId="" beanClass="com.install4j.runtime.beans.screens.StartupScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.StartupScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="33" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" enabled="true" commentSet="false" comment="" actionElevationType="inherit" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="34" customizedId="" beanClass="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents />
+          </screen>
+        </startup>
+        <screens>
+          <screen name="" id="24" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="41" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="25" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:welcomeMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript>!context.isConsole()</visibilityScript>
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="26" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
+                      <void property="consoleScript">
+                        <object class="com.install4j.api.beans.ScriptProperty">
+                          <void property="value">
+                            <string>String message = context.getMessage("ConfirmUninstall", context.getApplicationName());
+return console.askYesNo(message, true);
+</string>
+                          </void>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="27" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallationScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="false" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallationScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions>
+              <action name="" id="659" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="progressChangeType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.actions.control.ProgressChangeType</class>
+                          <string>SET_INDETERMINATE</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="29" customizedId="" beanClass="com.install4j.runtime.beans.actions.UninstallFilesAction" enabled="true" commentSet="false" comment="" actionElevationType="elevated" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.UninstallFilesAction" />
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+              <action name="" id="660" customizedId="" beanClass="com.install4j.runtime.beans.actions.control.SetProgressAction" enabled="true" commentSet="false" comment="" actionElevationType="none" rollbackBarrier="false" rollbackBarrierExitCode="0" multiExec="false" failureStrategy="1" errorMessage="">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.actions.control.SetProgressAction">
+                      <void property="percentValue">
+                        <int>100</int>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <condition />
+              </action>
+            </actions>
+            <formComponents>
+              <formComponent name="" id="28" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.ProgressComponent">
+                      <void property="initialStatusMessage">
+                        <string>${i18n:UninstallerPreparing}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+          <screen name="" id="32" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallFailureScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallFailureScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents />
+          </screen>
+          <screen name="" id="30" customizedId="" beanClass="com.install4j.runtime.beans.screens.UninstallSuccessScreen" enabled="true" commentSet="false" comment="" actionElevationType="inherit" styleId="41" rollbackBarrier="false" rollbackBarrierExitCode="0" backButton="2" finishScreen="true" wizardIndexChangeType="unchanged" wizardIndexKey="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.screens.UninstallSuccessScreen" />
+              </java>
+            </serializedBean>
+            <styleOverrides />
+            <condition />
+            <validation />
+            <preActivation />
+            <postActivation />
+            <actions />
+            <formComponents>
+              <formComponent name="" id="31" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="10" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
+                      <void property="labelText">
+                        <string>${form:successMessage}</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </formComponents>
+          </screen>
+        </screens>
+      </application>
+    </applications>
+    <styles defaultStyleId="35">
+      <style name="Standard" id="35" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <formComponent name="Header" id="36" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>48</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+          <group name="Main" id="37" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="imageEdgeAxisType">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.runtime.beans.formcomponents.AxisType</class>
+                      <string>HORIZONTAL</string>
+                    </object>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>../../resources/images/jalview_logo_background_fade-640x480.png</string>
+                    </object>
+                  </void>
+                  <void property="imageOverlap">
+                    <boolean>true</boolean>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="38" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="20" insetBottom="10" insetRight="20" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="Watermark" id="39" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="5" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="true" externalParametrizationName="Jalview" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent">
+                      <void property="enabledTitleText">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="labelText">
+                        <string>install4j</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames>
+                  <propertyName>labelText</propertyName>
+                </externalParametrizationPropertyNames>
+              </formComponent>
+              <formComponent name="Footer" id="40" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                      <void property="styleId">
+                        <string>52</string>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames />
+          </group>
+        </formComponents>
+      </style>
+      <style name="Banner" id="41" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.styles.FormStyle" />
+          </java>
+        </serializedBean>
+        <formComponents>
+          <group name="" id="42" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                  <void property="backgroundColor">
+                    <object class="java.awt.Color">
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="borderSides">
+                    <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                      <void property="bottom">
+                        <boolean>true</boolean>
+                      </void>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBackgroundColor">
+                    <object class="java.awt.Color">
+                      <int>25</int>
+                      <int>143</int>
+                      <int>220</int>
+                      <int>255</int>
+                    </object>
+                  </void>
+                  <void property="imageEdgeBorder">
+                    <boolean>true</boolean>
+                  </void>
+                  <void property="imageFile">
+                    <object class="com.install4j.api.beans.ExternalFile">
+                      <string>${compiler:sys.install4jHome}/resource/styles/wizard.png</string>
+                    </object>
+                  </void>
+                  <void property="insets">
+                    <object class="java.awt.Insets">
+                      <int>5</int>
+                      <int>10</int>
+                      <int>10</int>
+                      <int>10</int>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <beans>
+              <formComponent name="" id="43" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="0" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                      <void property="labelFontSizePercent">
+                        <int>130</int>
+                      </void>
+                      <void property="labelFontStyle">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                          <string>BOLD</string>
+                        </object>
+                      </void>
+                      <void property="labelFontType">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                          <string>DERIVED</string>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="44" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.formcomponents.SeparatorComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+              <formComponent name="" id="45" customizedId="" beanClass="com.install4j.runtime.beans.styles.ContentComponent" enabled="true" commentSet="false" comment="" insetTop="10" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.styles.ContentComponent" />
+                  </java>
+                </serializedBean>
+                <initScript />
+                <visibilityScript />
+                <externalParametrizationPropertyNames />
+              </formComponent>
+            </beans>
+            <externalParametrizationPropertyNames>
+              <propertyName>imageAnchor</propertyName>
+              <propertyName>imageEdgeBackgroundColor</propertyName>
+              <propertyName>imageFile</propertyName>
+            </externalParametrizationPropertyNames>
+          </group>
+          <formComponent name="" id="46" customizedId="" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="0" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.NestedStyleComponent">
+                  <void property="styleId">
+                    <string>52</string>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <initScript />
+            <visibilityScript />
+            <externalParametrizationPropertyNames />
+          </formComponent>
+        </formComponents>
+      </style>
+      <group name="Style components" id="47" customizedId="" beanClass="com.install4j.runtime.beans.groups.StyleGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit">
+        <serializedBean>
+          <java class="java.beans.XMLDecoder">
+            <object class="com.install4j.runtime.beans.groups.StyleGroup" />
+          </java>
+        </serializedBean>
+        <beans>
+          <style name="Standard header" id="48" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>NORTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="49" customizedId="" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="true" externalParametrizationName="Customize title bar" externalParametrizationMode="include">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
+                      <void property="backgroundColor">
+                        <object class="java.awt.Color">
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                          <int>255</int>
+                        </object>
+                      </void>
+                      <void property="borderSides">
+                        <object class="com.install4j.runtime.beans.formcomponents.BorderSides">
+                          <void property="bottom">
+                            <boolean>true</boolean>
+                          </void>
+                        </object>
+                      </void>
+                      <void property="imageAnchor">
+                        <object class="java.lang.Enum" method="valueOf">
+                          <class>com.install4j.api.beans.Anchor</class>
+                          <string>NORTHEAST</string>
+                        </object>
+                      </void>
+                      <void property="imageEdgeBorderWidth">
+                        <int>2</int>
+                      </void>
+                      <void property="imageFile">
+                        <object class="com.install4j.api.beans.ExternalFile">
+                          <string>icon:${installer:sys.installerApplicationMode}_header.png</string>
+                        </object>
+                      </void>
+                      <void property="imageInsets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>5</int>
+                          <int>1</int>
+                          <int>1</int>
+                        </object>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>0</int>
+                          <int>20</int>
+                          <int>0</int>
+                          <int>10</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="Title" id="50" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="labelFontStyle">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontStyle</class>
+                              <string>BOLD</string>
+                            </object>
+                          </void>
+                          <void property="labelFontType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.formcomponents.FontType</class>
+                              <string>DERIVED</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Subtitle" id="51" customizedId="" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="8" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.ScreenTitleComponent">
+                          <void property="titleType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.runtime.beans.styles.TitleType</class>
+                              <string>SUB_TITLE</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames>
+                  <propertyName>backgroundColor</propertyName>
+                  <propertyName>foregroundColor</propertyName>
+                  <propertyName>imageAnchor</propertyName>
+                  <propertyName>imageFile</propertyName>
+                  <propertyName>imageOverlap</propertyName>
+                </externalParametrizationPropertyNames>
+              </group>
+            </formComponents>
+          </style>
+          <style name="Standard footer" id="52" customizedId="" beanClass="com.install4j.runtime.beans.styles.FormStyle" enabled="true" commentSet="false" comment="">
+            <serializedBean>
+              <java class="java.beans.XMLDecoder">
+                <object class="com.install4j.runtime.beans.styles.FormStyle">
+                  <void property="fillVertical">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="standalone">
+                    <boolean>false</boolean>
+                  </void>
+                  <void property="verticalAnchor">
+                    <object class="java.lang.Enum" method="valueOf">
+                      <class>com.install4j.api.beans.Anchor</class>
+                      <string>SOUTH</string>
+                    </object>
+                  </void>
+                </object>
+              </java>
+            </serializedBean>
+            <formComponents>
+              <group name="" id="53" customizedId="" beanClass="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup" enabled="true" commentSet="false" comment="" actionElevationType="inherit" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                <serializedBean>
+                  <java class="java.beans.XMLDecoder">
+                    <object class="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup">
+                      <void property="alignFirstLabel">
+                        <boolean>false</boolean>
+                      </void>
+                      <void property="insets">
+                        <object class="java.awt.Insets">
+                          <int>3</int>
+                          <int>5</int>
+                          <int>8</int>
+                          <int>5</int>
+                        </object>
+                      </void>
+                    </object>
+                  </java>
+                </serializedBean>
+                <beans>
+                  <formComponent name="" id="54" customizedId="" beanClass="com.install4j.runtime.beans.formcomponents.SpringComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.formcomponents.SpringComponent" />
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Back button" id="55" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>&lt; ${i18n:ButtonBack}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>PREVIOUS</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Next button" id="56" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonNext} &gt;</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>NEXT</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                  <formComponent name="Cancel button" id="57" customizedId="" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" enabled="true" commentSet="false" comment="" insetTop="" insetLeft="5" insetBottom="" insetRight="" resetInitOnPrevious="false" useExternalParametrization="false" externalParametrizationName="" externalParametrizationMode="all">
+                    <serializedBean>
+                      <java class="java.beans.XMLDecoder">
+                        <object class="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
+                          <void property="buttonText">
+                            <string>${i18n:ButtonCancel}</string>
+                          </void>
+                          <void property="controlButtonType">
+                            <object class="java.lang.Enum" method="valueOf">
+                              <class>com.install4j.api.context.ControlButtonType</class>
+                              <string>CANCEL</string>
+                            </object>
+                          </void>
+                        </object>
+                      </java>
+                    </serializedBean>
+                    <initScript />
+                    <visibilityScript />
+                    <externalParametrizationPropertyNames />
+                  </formComponent>
+                </beans>
+                <externalParametrizationPropertyNames />
+              </group>
+            </formComponents>
+          </style>
+        </beans>
+      </group>
+    </styles>
+  </installerGui>
+  <mediaSets>
+    <windows name="Windows" id="130" customizedId="" mediaFileName="" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="windows-amd64-11.0.2" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="121" />
+        <launcher id="737" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+        <entry location=".i4j_fileset_1015" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+    <macos name="macOS Single Bundle Archive with Getdown JRE" id="131" customizedId="" mediaFileName="${compiler:sys.shortName}_${compiler:sys.platform}-installer_${compiler:sys.version}" installDir="${compiler:sys.fullName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="macosx-amd64-1.8.0_202_unpacked" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" requiredVmIdPrefix="" customInstallBaseDir="" contentFilesType="1" installerName="${i18n:InstallerName(${compiler:sys.fullName})}" volumeName="${compiler:sys.shortName}" compressDmg="true" launcherId="121">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles />
+    </macos>
+    <unixInstaller name="Unix Installer" id="132" customizedId="" mediaFileName="" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="737" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <installerScript mode="1" file="">
+        <content />
+      </installerScript>
+    </unixInstaller>
+    <macosArchive name="macOS Single Bundle Archive" id="152" customizedId="" mediaFileName="${compiler:sys.shortName}_${compiler:sys.platform}-app_${compiler:sys.version}" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="macosx-amd64-11.0.2" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName}" launcherId="121">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="../../resources/install4j/jalview_dmg_background.png" />
+        <file name=".DS_Store" file="../../resources/install4j/DS_Store" />
+      </topLevelFiles>
+    </macosArchive>
+    <linuxDeb name="Linux Deb Archive" id="153" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" bzip="true" description="Jalview Desktop" maintainerEmail="help@jalview.org" architectureSet="false" architecture="">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="121" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxDeb>
+    <linuxRPM name="Linux RPM" id="570" customizedId="" mediaFileName="" installDir="/opt/${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" overwriteNeverAsConfigFiles="false" dependencies="" os="linux" arch="i386">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="121" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <preInstallScript mode="1" file="">
+        <content />
+      </preInstallScript>
+      <postInstallScript mode="1" file="">
+        <content />
+      </postInstallScript>
+      <preUninstallScript mode="1" file="">
+        <content />
+      </preUninstallScript>
+      <postUninstallScript mode="1" file="">
+        <content />
+      </postUninstallScript>
+    </linuxRPM>
+    <macosArchive name="Offline macOS Single Bundle Archive" id="739" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}-app_${compiler:sys.version}" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="macosx-amd64-11.0.2" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Offline Installer" launcherId="737">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="../../resources/install4j/jalview_dmg_background.png" />
+        <file name=".DS_Store" file="../../resources/install4j/DS_Store" />
+      </topLevelFiles>
+    </macosArchive>
+    <unixInstaller name="Offline Unix Installer" id="741" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_${compiler:sys.version}" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="121" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <installerScript mode="1" file="">
+        <content />
+      </installerScript>
+    </unixInstaller>
+    <windows name="Offline Windows" id="743" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}_${compiler:sys.version}" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="64" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="false" includedJRE="" manualJREEntry="false" bundleType="1" jreURL="" jreShared="false" directDownload="false" installOnlyIfNecessary="false" customInstallBaseDir="" contentFilesType="1" verifyIntegrity="true">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedLaunchers>
+        <launcher id="121" />
+        <launcher id="737" />
+      </excludedLaunchers>
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_734" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+        <entry location=".i4j_fileset_1015" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+    </windows>
+    <macosArchive name="Offline macOS Single Bundle Archive with Getdown JRE" id="878" customizedId="" mediaFileName="${compiler:sys.shortName}-OFFLINE_${compiler:sys.platform}-app_${compiler:sys.version}" installDir="${compiler:sys.shortName}" overridePrincipalLanguage="false" jreBitType="all" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" useLegacyMediaFileIds="false" legacyMediaFileIds="" downloadURL="" includeAllDownloadableComponents="true" includedJRE="" manualJREEntry="false" archiveType="dmg" volumeName="${compiler:sys.shortName} Offline Installer" launcherId="737">
+      <excludedComponents />
+      <includedDownloadableComponents />
+      <excludedBeans />
+      <overriddenPrincipalLanguage id="en" customLocalizationFile="" />
+      <exclude>
+        <entry location=".i4j_fileset_" fileType="regular" />
+        <entry location=".i4j_fileset_880" fileType="regular" />
+        <entry location=".i4j_fileset_882" fileType="regular" />
+      </exclude>
+      <variables />
+      <autoUpdate useMinUpdatableVersion="false" minUpdatableVersion="" useMaxUpdatableVersion="false" maxUpdatableVersion="">
+        <commentFiles />
+        <customAttributes />
+      </autoUpdate>
+      <topLevelFiles>
+        <symlink name="&quot; &quot;" target="/Applications" />
+        <file name=".background/jalview_dmg_background.png" file="./jalview_dmg_background.png" />
+        <file name=".DS_Store" file="./DS_Store" />
+      </topLevelFiles>
+    </macosArchive>
+  </mediaSets>
+  <buildIds buildAll="false">
+    <mediaSet refId="153" />
+    <mediaSet refId="570" />
+    <mediaSet refId="743" />
+    <mediaSet refId="878" />
+  </buildIds>
+  <buildOptions verbose="true" faster="true" disableSigning="true" disableJreBundling="false" debug="false" />
+</install4j>
diff --git a/utils/jdeps_jlink_all.sh b/utils/jdeps_jlink_all.sh
new file mode 100755 (executable)
index 0000000..9f79d2d
--- /dev/null
@@ -0,0 +1,23 @@
+#/usr/bin/env bash
+
+# Be in the jalview top level dir.
+# lib -- contains usual jalview jar files
+#        make sure it contains the extra jar files needed in classpath for Java 11
+# j11lib -- contains java11 style modules to be put into the JRE (not needed at runtime)
+#
+# j11jre -- dir containing JRE environments for jalview
+#
+# creates file modules.new which is comma-separated list of modules needed, can be used like this in jlink argument
+# and a java 11 JRE in j11jre/jre-new
+
+( for x in lib/*.jar j11lib/*.jar dist/jalview.jar; do echo $x >&2; jdeps --list-deps --module-path j11lib $x | grep -v Warning: | grep -v "JDK removed" | sed -e 's/^ *//;s/\/.*//;s/$/,/;'; done ) | sort -u | perl -p -e 'chomp;' | perl -p -e 's/,$//;chomp;' > modules.new
+
+if [ x$JAVA_HOME != x ]; then
+  jlink --no-header-files --no-man-pages --strip-debug --module-path "$JAVA_HOME/jmods:j11lib" --add-modules `cat modules.new` --compress=2 --output j11jre/jre-new
+else
+  jlink --no-header-files --no-man-pages --strip-debug --module-path "j11lib" --add-modules `cat modules.new` --compress=2 --output j11jre/jre-new
+fi
+
+
+# or if you're in a hurry for a one-liner...
+#jlink --no-header-files --no-man-pages --strip-debug --module-path "$JAVA_HOME/jmods:j11lib" --add-modules ` ( for x in lib/*.jar j11lib/*.jar dist/jalview.jar; do echo $x >&2; jdeps --list-deps --module-path j11mod $x | grep -v "Warning:" | grep -v "JDK removed" | sed -e 's/^ *//;s/\/.*//;s/$/,/;'; done ) | sort -u | perl -p -e 'chomp;' | perl -p -e 's/,$//;chomp;' ` --compress=2 --output j11jre/jre-new
diff --git a/utils/modulify-jar.sh b/utils/modulify-jar.sh
new file mode 100755 (executable)
index 0000000..f508ed2
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+CMD=$(basename $0)
+
+usage() {
+  echo "Usage: $CMD /path/to/jarfile" >&2
+}
+
+usagexit() {
+  usage
+  exit 1
+}
+
+error() {
+  echo $1 >&2
+  usagexit
+}
+
+JARFILE=$1
+[ -z $JARFILE ] && usagexit
+[ -f $JARFILE ] || error "No file $JARFILE"
+[ -r $JARFILE ] || error "$JARFILE not readable"
+
+EXT=.jar
+SUFFIX=-MODULE
+FILENAME=$(basename $JARFILE)
+BASE=$(basename -s $EXT $JARFILE)
+DIR=$(dirname $JARFILE)
+
+# set absolute path to $JARFILE if not specified
+[ x${DIR#/} = x$DIR ] && DIR=$(cd "$DIR" && pwd)
+
+ABSJARFILE=$DIR/$FILENAME
+
+TMPDIR=/tmp/$USER-$CMD-$BASE-$$
+
+[ x$FILENAME = x$BASE ] && error "Should be $EXT file"
+
+mkdir -p $TMPDIR/jar || error "Could not create tmp dir $TMPDIR/jar"
+mkdir -p $TMPDIR/info || error "Could not create tmp dir $TMPDIR/info"
+cd $TMPDIR/jar
+jar -xvf $ABSJARFILE > /dev/null
+jdeps --module-path="$DIR" --generate-module-info $TMPDIR/info $ABSJARFILE
+# next line assuming only one module-info.java file created, I think this is always true...? It'll just use the last one if not.
+find $TMPDIR/info -name "module-info.java" -exec /bin/mv {} . \;
+[ -e ./module-info.java ] || error "No module-info.java file found in $TMPDIR/info"
+javac -d $TMPDIR/jar ./module-info.java
+jar -cvf $DIR/${BASE}${SUFFIX}${EXT} -C $TMPDIR/jar . > /dev/null
+rm -rf $TMPDIR
+
+
diff --git a/utils/showJVMVersion.java b/utils/showJVMVersion.java
new file mode 100644 (file)
index 0000000..671abc4
--- /dev/null
@@ -0,0 +1,5 @@
+public class showJVMVersion {
+ public static void main(String args[]) {
+   System.out.println(System.getProperty("java.version"));
+ }
+}